var createContext = React.createContext;
var useState = React.useState;
var useContext = React.useContext;
var useCallback = React.useCallback;
var useEffect = React.useEffect;
var useRef = React.useRef;
var useMemo = React.useMemo;

/**
 * ScanStore — React Context for scan + property state management.
 * Persists to IndexedDB via StorageService (localforage).
 *
 * Hydration: on mount, loads persisted data. Exposes `loading` flag.
 * Persistence: after every state mutation, writes to StorageService.
 *
 * Public API is unchanged — all existing UI components work without modification.
 */

var ScanContext = createContext(null);

// CR-16: Safer ID generation — uses crypto.randomUUID where available,
// falls back to timestamp + random for older browsers.
var generateId = function () {
    if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
        return crypto.randomUUID();
    }
    return Date.now().toString() + '-' + Math.random().toString(36).substr(2, 9);
};

var ScanProvider = function (props) {
    var scanState = useState([]);
    var scans = scanState[0];
    var setScans = scanState[1];

    var propState = useState([]);
    var properties = propState[0];
    var setProperties = propState[1];

    var loadingState = useState(true);
    var loading = loadingState[0];
    var setLoading = loadingState[1];

    // Track whether initial hydration is complete to avoid persisting the empty initial state
    var hydratedRef = useRef(false);

    // --- Hydrate from IndexedDB on mount ---
    useEffect(function () {
        Promise.all([
            StorageService.loadScans(),
            StorageService.loadProperties(),
        ]).then(function (results) {
            // Migrate legacy records that persisted heatmaps inline: move the
            // payload to its per-scan key (CR-02) and keep in-memory state
            // heatmap-free. persistAll strips the inline field on next write.
            var loaded = results[0];
            for (var i = 0; i < loaded.length; i++) {
                if (loaded[i].heatmapDataURL) {
                    StorageService.saveHeatmap(loaded[i].id, loaded[i].heatmapDataURL)['catch'](function () {});
                    var copy = {};
                    for (var k in loaded[i]) { copy[k] = loaded[i][k]; }
                    copy.heatmapDataURL = null;
                    loaded[i] = copy;
                }
            }
            setScans(loaded);
            setProperties(results[1]);
            hydratedRef.current = true;
            setLoading(false);
        })['catch'](function (err) {
            // Deliberately do NOT mark hydrated: persisting the empty initial
            // state after a failed load would overwrite the user's entire
            // stored dataset with near-empty arrays. Writes stay disabled for
            // this session; the data is intact for the next launch.
            console.error('StorageService hydration failed — persistence disabled for this session to protect stored data:', err);
            setLoading(false);
        });
    }, []);

    // --- Debounced persistence (CR-01 fix) ---
    // Coalesces rapid mutations into a single write after 300ms of quiet.
    // Uses StorageService.persistAll for serialised, atomic writes.
    var debounceRef = useRef(null);

    // Mirror of the latest state so the unload flush can write without
    // being part of the debounce effect's closure.
    var latestRef = useRef({ scans: scans, properties: properties });
    latestRef.current = { scans: scans, properties: properties };

    useEffect(function () {
        if (!hydratedRef.current) return;
        if (debounceRef.current) clearTimeout(debounceRef.current);
        debounceRef.current = setTimeout(function () {
            debounceRef.current = null;
            StorageService.persistAll(scans, properties)['catch'](function (err) {
                console.error('Failed to persist data:', err);
            });
        }, 300);
        return function () {
            if (debounceRef.current) clearTimeout(debounceRef.current);
        };
    }, [scans, properties]);

    // Flush any pending debounced write immediately. Called when the page is
    // being hidden/closed — otherwise a scan saved within the 300ms window is
    // lost when the mobile PWA is backgrounded.
    var flushPendingWrite = useCallback(function () {
        if (!debounceRef.current) return;
        clearTimeout(debounceRef.current);
        debounceRef.current = null;
        StorageService.persistAll(latestRef.current.scans, latestRef.current.properties)['catch'](function (err) {
            console.error('Failed to flush pending data on page hide:', err);
        });
    }, []);

    useEffect(function () {
        var onVisibilityChange = function () {
            if (document.visibilityState === 'hidden') flushPendingWrite();
        };
        window.addEventListener('pagehide', flushPendingWrite);
        document.addEventListener('visibilitychange', onVisibilityChange);
        return function () {
            window.removeEventListener('pagehide', flushPendingWrite);
            document.removeEventListener('visibilitychange', onVisibilityChange);
            flushPendingWrite(); // provider unmount
        };
    }, []);

    // --- Property methods (unchanged) ---

    var addProperty = useCallback(function (data) {
        var record = {
            id: generateId(),
            name: data.name || '',
            address: data.address || '',
            buildingType: data.buildingType || 'House',
            isPrimary: data.isPrimary || false,
            createdAt: Date.now(),
        };
        setProperties(function (prev) {
            if (record.isPrimary) {
                var updated = [];
                for (var i = 0; i < prev.length; i++) {
                    var copy = {};
                    for (var k in prev[i]) { copy[k] = prev[i][k]; }
                    copy.isPrimary = false;
                    updated.push(copy);
                }
                return updated.concat([record]);
            }
            if (prev.length === 0) {
                record.isPrimary = true;
            }
            return prev.concat([record]);
        });
        return record;
    }, []);

    var updateProperty = useCallback(function (id, fields) {
        setProperties(function (prev) {
            var result = [];
            for (var i = 0; i < prev.length; i++) {
                var copy = {};
                for (var k in prev[i]) { copy[k] = prev[i][k]; }
                if (copy.id === id) {
                    for (var f in fields) { copy[f] = fields[f]; }
                }
                if (fields.isPrimary && copy.id !== id) {
                    copy.isPrimary = false;
                }
                result.push(copy);
            }
            return result;
        });
    }, []);

    var deleteProperty = useCallback(function (id) {
        setProperties(function (prev) {
            var remaining = [];
            var wasPrimary = false;
            for (var i = 0; i < prev.length; i++) {
                if (prev[i].id === id) {
                    wasPrimary = prev[i].isPrimary;
                } else {
                    var copy = {};
                    for (var k in prev[i]) { copy[k] = prev[i][k]; }
                    remaining.push(copy);
                }
            }
            if (wasPrimary && remaining.length > 0) {
                remaining[0].isPrimary = true;
            }
            return remaining;
        });
        setScans(function (prev) {
            var result = [];
            for (var i = 0; i < prev.length; i++) {
                var copy = {};
                for (var k in prev[i]) { copy[k] = prev[i][k]; }
                if (copy.propertyId === id) { copy.propertyId = null; }
                result.push(copy);
            }
            return result;
        });
    }, []);

    // --- Scan methods (unchanged) ---

    var addScan = useCallback(function (scanData) {
        var record = {
            id: generateId(),
            timestamp: Date.now(),
            fileName: scanData.fileName || 'Untitled',
            thumbnail: scanData.thumbnail || '',
            // MR-07 FIX: strip previewURL from in-memory state — it is only needed
            // transiently in AnalysisPage during the analysis session. List views
            // (RecentScans, ScansPage, ReportsPage) all use thumbnail (~5-15KB),
            // not previewURL (~200-500KB). StorageService already strips it before
            // IndexedDB writes, so this mirrors that behaviour in memory.
            previewURL: '',
            percentage: scanData.percentage || 0,
            verdict: scanData.verdict || '',
            severity: scanData.severity || 'low',
            label: scanData.label || '',
            insight: scanData.insight || '',
            // Standard observation fields
            propertyId: scanData.propertyId || null,
            roomName: scanData.roomName || null,
            odourLevel: scanData.odourLevel || null,
            odourSource: scanData.odourSource || null,
            surfaceTextures: scanData.surfaceTextures || [],
            severityRating: (scanData.severityRating != null) ? scanData.severityRating : 0,
            notes: scanData.notes || '',
            // Enterprise assessment fields (null for standard scans — backward compatible)
            assessmentComponents: scanData.assessmentComponents || null,
            environmentalReadings: scanData.environmentalReadings || null,
            affectedAreaM2: (scanData.affectedAreaM2 != null) ? scanData.affectedAreaM2 : null,
            riskClassification: scanData.riskClassification || null,
            remediationMethod: scanData.remediationMethod || null,
            containmentType: scanData.containmentType || null,
            ppeLevel: scanData.ppeLevel || null,
            technicianName: scanData.technicianName || null,
            technicianCertNo: scanData.technicianCertNo || null,
            assessmentDate: scanData.assessmentDate || null,
            // Local CNN extended fields (null for Nyckel scans — backward compatible)
            aiEngine: scanData.aiEngine || null,
            rawScore: (scanData.rawScore != null) ? scanData.rawScore : null,
            freqSpectrum: scanData.freqSpectrum || null,
            saliencyLevel: scanData.saliencyLevel || null,
            saliencyPeakToMean: (scanData.saliencyPeakToMean != null) ? scanData.saliencyPeakToMean : null,
            isShortcut: scanData.isShortcut || false,
            // CR-02 FIX: heatmapDataURL stripped from in-memory state to prevent
            // base64 accumulation across scans. Written directly to IndexedDB
            // below (per-scan key) and loaded on-demand by ScanDetails via
            // StorageService.getScanById().
            heatmapDataURL: null,
            speciesTop: scanData.speciesTop || null,
            speciesRanked: scanData.speciesRanked || [],
            speciesIsUncertain: scanData.speciesIsUncertain || false,
            speciesSummary: scanData.speciesSummary || '',
            // M9: New diagnostic fields (H4/H5/H6 — null for Nyckel/legacy scans)
            darkMouldConsensus: scanData.darkMouldConsensus || false,
            patchContribution: (scanData.patchContribution != null) ? scanData.patchContribution : null,
            textureIrregularity: (scanData.textureIrregularity != null) ? scanData.textureIrregularity : null,
            textureIrregularityLevel: scanData.textureIrregularityLevel || null,
            interChannelDiff: (scanData.interChannelDiff != null) ? scanData.interChannelDiff : null,
        };
        if (scanData.heatmapDataURL) {
            StorageService.saveHeatmap(record.id, scanData.heatmapDataURL)['catch'](function (err) {
                console.error('Failed to persist heatmap for scan ' + record.id + ':', err);
            });
        }
        setScans(function (prev) { return [record].concat(prev); });
        return record;
    }, []);

    var updateLatestScan = useCallback(function (fields) {
        setScans(function (prev) {
            if (prev.length === 0) return prev;
            var updated = {};
            for (var k in prev[0]) { updated[k] = prev[0][k]; }
            for (var f in fields) { updated[f] = fields[f]; }
            return [updated].concat(prev.slice(1));
        });
    }, []);

    var updateScan = useCallback(function (scanId, fields) {
        // CR-02: heatmaps never enter in-memory state — divert them to their
        // per-scan IndexedDB key (hybrid enrichment patches arrive this way).
        if (fields.heatmapDataURL) {
            StorageService.saveHeatmap(scanId, fields.heatmapDataURL)['catch'](function (err) {
                console.error('Failed to persist heatmap for scan ' + scanId + ':', err);
            });
            var stripped = {};
            for (var sf in fields) {
                stripped[sf] = (sf === 'heatmapDataURL') ? null : fields[sf];
            }
            fields = stripped;
        }
        setScans(function (prev) {
            var result = [];
            for (var i = 0; i < prev.length; i++) {
                var copy = {};
                for (var k in prev[i]) { copy[k] = prev[i][k]; }
                if (copy.id === scanId) {
                    for (var f in fields) { copy[f] = fields[f]; }
                }
                result.push(copy);
            }
            return result;
        });
    }, []);

    var deleteScan = useCallback(function (scanId) {
        StorageService.removeHeatmap(scanId)['catch'](function () {});
        setScans(function (prev) {
            var result = [];
            for (var i = 0; i < prev.length; i++) {
                if (prev[i].id !== scanId) result.push(prev[i]);
            }
            return result;
        });
    }, []);

    // CR-03: Split context into stable methods vs reactive state for optimal re-rendering.
    // Methods are memoized with empty deps (stable references), while reactive state
    // is memoized separately. This prevents method recreation on every state change.
    
    // Stable method references - never change after component mount
    var stableMethods = React.useMemo(function () {
        return {
            addScan: addScan,
            updateScan: updateScan,
            deleteScan: deleteScan,
            updateLatestScan: updateLatestScan,
            addProperty: addProperty,
            updateProperty: updateProperty,
            deleteProperty: deleteProperty
        };
    }, []); // Empty deps - these callbacks are stable
    
    // Reactive state with derived getters - changes when data changes
    var reactiveState = React.useMemo(function () {
        return {
            loading: loading,
            scans: scans,
            properties: properties,
            // Derived getters computed fresh when data changes
            getLatestScan: function() { return scans.length > 0 ? scans[0] : null; },
            getScanCount: function() { return scans.length; },
            getAverageRisk: function() {
                if (scans.length === 0) return { percentage: 0, severity: 'low', label: 'Unrated' };
                var sum = 0;
                for (var i = 0; i < scans.length; i++) { sum += scans[i].percentage; }
                var avg = Math.round(sum / scans.length);
                var derived = SeverityConfig.fromPercentage(avg);
                return { percentage: avg, severity: derived.severity, label: derived.label };
            },
            getScansByRoom: function() { return GroupingUtils.groupByRoom(scans); },
            getScansForProperty: function(propertyId) {
                var result = [];
                for (var i = 0; i < scans.length; i++) {
                    if (scans[i].propertyId === propertyId) result.push(scans[i]);
                }
                return result;
            },
            getProperties: function() { return properties; },
            getPrimaryProperty: function() {
                for (var i = 0; i < properties.length; i++) {
                    if (properties[i].isPrimary) return properties[i];
                }
                return properties.length > 0 ? properties[0] : null;
            },
            getPropertyById: function(id) {
                for (var i = 0; i < properties.length; i++) {
                    if (properties[i].id === id) return properties[i];
                }
                return null;
            }
        };
    }, [loading, scans, properties]);
    
    // Combined context value - methods + reactive state
    var contextValue = React.useMemo(function () {
        return Object.assign({}, stableMethods, reactiveState);
    }, [stableMethods, reactiveState]);

    return React.createElement(ScanContext.Provider, { value: contextValue }, props.children);
};

var useScanStore = function () {
    var ctx = useContext(ScanContext);
    if (!ctx) throw new Error('useScanStore must be used within a ScanProvider');
    return ctx;
};

window.ScanProvider = ScanProvider;
window.useScanStore = useScanStore;
