/**
 * AIEngineAdapter — Configurable adapter for AI mould detection engines.
 *
 * Implements the Strategy pattern: each engine is an object conforming to the
 * EngineStrategy interface. AnalysisPage calls AIEngineAdapter.resolve() once,
 * receives a strategy, and calls strategy.analyze() — engine details are fully
 * encapsulated here. Adding a new external AI provider requires only a new
 * strategy object; no changes to AnalysisPage, ScanStore, or any UI component.
 *
 * Engine priority (read from AI_ENGINE feature flag):
 *   'local'   → LocalCNNStrategy   — full on-device pipeline
 *   'hybrid'  → HybridStrategy     — Nyckel verdict + Local CNN enrichment
 *   'nyckel'  → NyckelStrategy     — cloud detection only
 *   'cloud'   → CloudStrategy      — future bespoke API (falls through to Nyckel)
 *   'none'    → NoneStrategy       — error state
 *
 * EngineResult contract (superset of all engine outputs):
 * {
 *   // Core detection — all engines
 *   percentage      number       0–100 confidence
 *   verdict         string       'Very Likely Mould' | 'Possible Mould' | 'Uncertain' | 'Unlikely Mould'
 *   severity        string       'critical' | 'high' | 'moderate' | 'low'
 *   label           string       'Mould' | 'No Mould'
 *   insight         string       contextual explanation text
 *   aiEngine        string       'local_cnn' | 'hybrid' | 'nyckel' | 'cloud_ai'
 *
 *   // Enrichment — Local CNN and Hybrid engines only (null for plain Nyckel)
 *   hasEnrichment   boolean      true when enrichment fields are present
 *   freqSpectrum    object|null  { low, mid, high, signature, description }
 *   saliencyLevel   string|null  'HIGH' | 'MODERATE' | 'DIFFUSE'
 *   saliencyPeakToMean number|null
 *   isShortcut      boolean      border attention warning
 *   heatmapDataURL  string|null  base64 JPEG overlay
 *   speciesTop      object|null  { label, confidence, description, color }
 *   speciesRanked   array        [{ label, confidence, color, description }]
 *   speciesIsUncertain boolean
 *   speciesSummary  string
 *
 *   // Hybrid-only state
 *   enrichmentPending  boolean   true while Local CNN pass is still running
 *   enrichmentFailed   boolean   true if Local CNN pass failed (verdict still valid)
 *
 *   // Diagnostic fields — Local CNN only (null otherwise)
 *   rawScore            number|null
 *   darkMouldConsensus  boolean
 *   patchContribution   number|null
 *   textureIrregularity number|null
 *   textureIrregularityLevel string|null
 *   interChannelDiff    number|null
 * }
 *
 * Depends on: FeatureFlags, MouldDetectService, LocalCNNService, ThumbnailService
 * ES5 — no const, let, arrow functions, template literals, optional chaining.
 */
var AIEngineAdapter = (function () {

    // ── Null-safe enrichment defaults ────────────────────────────────────────
    // Applied when an engine does not produce enrichment fields.
    // Ensures EngineResult shape is always complete.

    var EMPTY_ENRICHMENT = {
        hasEnrichment:          false,
        freqSpectrum:           null,
        saliencyLevel:          null,
        saliencyPeakToMean:     null,
        isShortcut:             false,
        heatmapDataURL:         null,
        speciesTop:             null,
        speciesRanked:          [],
        speciesIsUncertain:     false,
        speciesSummary:         '',
        enrichmentPending:      false,
        enrichmentFailed:       false,
        rawScore:               null,
        darkMouldConsensus:     false,
        patchContribution:      null,
        textureIrregularity:    null,
        textureIrregularityLevel: null,
        interChannelDiff:       null
    };

    // ── Merge helper ─────────────────────────────────────────────────────────

    function _merge(base, overrides) {
        var result = {};
        var k;
        for (k in base)      { result[k] = base[k]; }
        for (k in overrides) { result[k] = overrides[k]; }
        return result;
    }

    // ── Strategy: LocalCNNStrategy ────────────────────────────────────────────
    // Full on-device pipeline. Returns complete EngineResult including all
    // enrichment fields in a single Promise.

    var LocalCNNStrategy = {
        engineId: 'local_cnn',

        analyze: function (imageData, onStage) {
            var imgEl = imageData.imgElement;
            var file  = imageData.file;

            // MR-05 FIX: guard both paths — without imgElement the heatmap canvas
            // fails; without file the tensor path falls back to imgElement which
            // may be null. Provide a user-friendly message rather than a TF.js crash.
            if (!imgEl && !file) {
                return Promise.reject(new Error('Image not ready. Please go back and select your photo again.'));
            }
            if (LocalCNNService.getLoadState() !== 'ready') {
                return Promise.reject(new Error('The AI engine is still loading. Please wait a moment and try again.'));
            }

            return LocalCNNService.analyze(imgEl, onStage, file)
                .then(function (res) {
                    return _merge(EMPTY_ENRICHMENT, {
                        // Core
                        percentage:             res.percentage,
                        verdict:                res.verdict,
                        severity:               res.severity,
                        label:                  res.label,
                        insight:                res.insight,
                        aiEngine:               'local_cnn',
                        // Enrichment
                        hasEnrichment:          true,
                        freqSpectrum:           res.freqSpectrum          || null,
                        saliencyLevel:          res.saliencyLevel         || null,
                        saliencyPeakToMean:     res.saliencyPeakToMean    || null,
                        isShortcut:             res.isShortcut            || false,
                        heatmapDataURL:         res.heatmapDataURL        || null,
                        speciesTop:             res.speciesTop            || null,
                        speciesRanked:          res.speciesRanked         || [],
                        speciesIsUncertain:     res.speciesIsUncertain    || false,
                        speciesSummary:         res.speciesSummary        || '',
                        enrichmentPending:      false,
                        enrichmentFailed:       false,
                        // Diagnostics
                        rawScore:               res.rawScore              || null,
                        darkMouldConsensus:     res.darkMouldConsensus    || false,
                        patchContribution:      res.patchContribution     || null,
                        textureIrregularity:    res.textureIrregularity   || null,
                        textureIrregularityLevel: res.textureIrregularityLevel || null,
                        interChannelDiff:       res.interChannelDiff      || null
                    });
                });
        }
    };

    // ── Strategy: NyckelStrategy ──────────────────────────────────────────────
    // Cloud detection only. No enrichment fields.

    var NyckelStrategy = {
        engineId: 'nyckel',

        analyze: function (imageData, onStage) {
            var notify = onStage || function () {};
            notify('Analysing image…');

            return MouldDetectService.analyze(imageData.dataURL)
                .then(function (res) {
                    return _merge(EMPTY_ENRICHMENT, {
                        percentage: res.percentage,
                        verdict:    res.verdict,
                        severity:   res.severity,
                        label:      res.label,
                        insight:    res.insight,
                        aiEngine:   'nyckel'
                    });
                });
        }
    };

    // ── Strategy: HybridStrategy ──────────────────────────────────────────────
    // Nyckel provides the primary verdict. Local CNN enrichment runs in parallel
    // and is delivered via an onEnrichment callback once complete.
    //
    // analyze() resolves immediately with the Nyckel result and
    // enrichmentPending=true. The caller (AnalysisPage) also receives the
    // enrichment Promise via result._enrichmentPromise and calls updateScan()
    // when it resolves. This two-phase pattern gives the user immediate feedback
    // while enrichment panels load progressively.

    var HybridStrategy = {
        engineId: 'hybrid',

        analyze: function (imageData, onStage) {
            var notify = onStage || function () {};
            notify('Analysing image…');

            // HR-04 FIX: cancellation flag shared between Nyckel and enrichment.
            // If Nyckel rejects, cancelled=true stops the enrichment result from
            // patching state after the error phase has already been shown.
            var cancelled = false;

            // Phase 1: Nyckel call — resolves quickly
            var nyckelPromise = MouldDetectService.analyze(imageData.dataURL);

            // Phase 2: Local CNN enrichment — starts in parallel, resolves later.
            // LocalCNNEnrichmentService is loaded only when AI_ENGINE='hybrid'.
            // If it is not available (not yet loaded / load failed), we resolve
            // with an empty enrichment so the Hybrid result is still usable.
            var enrichmentPromise;
            if (typeof window.LocalCNNEnrichmentService !== 'undefined' &&
                window.LocalCNNEnrichmentService.isReady()) {
                enrichmentPromise = window.LocalCNNEnrichmentService.enrich(
                    imageData.imgElement,
                    imageData.file,
                    function (msg) { notify('Enriching — ' + msg); }
                )
                .then(function (result) {
                    if (cancelled) return null; // HR-04: discard if Nyckel already failed
                    return result;
                })
                ['catch'](function (err) {
                    console.warn('[HybridStrategy] Enrichment failed, proceeding with Nyckel result only:', err.message);
                    return null;
                });
            } else {
                console.warn('[HybridStrategy] LocalCNNEnrichmentService not ready. Enrichment skipped.');
                enrichmentPromise = Promise.resolve(null);
            }

            return nyckelPromise
                ['catch'](function (err) {
                    cancelled = true; // HR-04: stop enrichment from patching state
                    throw err;
                })
                .then(function (nyckelRes) {
                // Immediate result with enrichmentPending=true
                var immediateResult = _merge(EMPTY_ENRICHMENT, {
                    percentage:        nyckelRes.percentage,
                    verdict:           nyckelRes.verdict,
                    severity:          nyckelRes.severity,
                    label:             nyckelRes.label,
                    insight:           nyckelRes.insight,
                    aiEngine:          'hybrid',
                    enrichmentPending: true,
                    enrichmentFailed:  false
                });

                // Attach the enrichment promise so AnalysisPage can chain it
                // without needing to know about LocalCNNEnrichmentService.
                // This is not part of the EngineResult contract — it is a
                // transport mechanism between the strategy and AnalysisPage.
                immediateResult._enrichmentPromise = enrichmentPromise.then(function (enrichment) {
                    if (!enrichment) {
                        // Enrichment failed or service unavailable
                        return _merge(immediateResult, {
                            enrichmentPending: false,
                            enrichmentFailed:  true,
                            _enrichmentPromise: undefined
                        });
                    }
                    return _merge(immediateResult, {
                        hasEnrichment:          true,
                        freqSpectrum:           enrichment.freqSpectrum          || null,
                        saliencyLevel:          enrichment.saliencyLevel         || null,
                        saliencyPeakToMean:     enrichment.saliencyPeakToMean    || null,
                        isShortcut:             enrichment.isShortcut            || false,
                        heatmapDataURL:         enrichment.heatmapDataURL        || null,
                        speciesTop:             enrichment.speciesTop            || null,
                        speciesRanked:          enrichment.speciesRanked         || [],
                        speciesIsUncertain:     enrichment.speciesIsUncertain    || false,
                        speciesSummary:         enrichment.speciesSummary        || '',
                        enrichmentPending:      false,
                        enrichmentFailed:       false,
                        _enrichmentPromise:     undefined
                    });
                });

                return immediateResult;
            });
        }
    };

    // ── Strategy: CloudStrategy ───────────────────────────────────────────────
    // Placeholder for bespoke Mould Detect cloud CNN (AWS Lambda/SageMaker).
    // Falls through to NyckelStrategy until CloudAIService is implemented.

    var CloudStrategy = {
        engineId: 'cloud_ai',

        analyze: function (imageData, onStage) {
            console.info('[CloudStrategy] Bespoke cloud CNN not yet implemented. Falling through to Nyckel.');
            return NyckelStrategy.analyze(imageData, onStage);
        }
    };

    // ── Strategy: NoneStrategy ────────────────────────────────────────────────
    // All engines disabled. Returns a rejected Promise so AnalysisPage
    // transitions to the error phase.

    var NoneStrategy = {
        engineId: 'none',

        analyze: function () {
            return Promise.reject(new Error('No AI engine is currently enabled. Set AI_ENGINE to a valid value in feature-flags.json.'));
        }
    };

    // ── Strategy registry ────────────────────────────────────────────────────
    // Maps AI_ENGINE flag values to strategy objects.
    // Add new external AI providers here — one entry, no other changes needed.

    var STRATEGY_REGISTRY = {
        'local':   LocalCNNStrategy,
        'hybrid':  HybridStrategy,
        'nyckel':  NyckelStrategy,
        'cloud':   CloudStrategy,
        'none':    NoneStrategy
    };

    // ── Public API ────────────────────────────────────────────────────────────

    /**
     * resolve() — Returns the active engine strategy based on AI_ENGINE flag.
     *
     * Reads AI_ENGINE at call time (not cached) so flag changes take effect
     * without a page reload during development.
     *
     * @returns {EngineStrategy} — one of the strategy objects above
     */
    function resolve() {
        var engineValue = FeatureFlags.getValue('AI_ENGINE') || 'nyckel';
        var strategy = STRATEGY_REGISTRY[engineValue];
        if (!strategy) {
            console.warn('[AIEngineAdapter] Unknown AI_ENGINE value: "' + engineValue + '". Falling back to Nyckel.');
            return NyckelStrategy;
        }
        return strategy;
    }

    return {
        resolve: resolve
    };
})();

window.AIEngineAdapter = AIEngineAdapter;
