// Director AI tab — two-card UI with auto-save (KL-48, Alpine
// conversion KL-129).
//
// Single source of truth: GET /api/director/config returns
// {enabled, byok_enabled, has_api_key, api_key_masked, tier,
//  active_path}. Every UI change writes immediately:
//   - enable toggle  → PUT /api/director/config {enabled}
//   - card selection → PUT /api/director/config {byok_enabled}
//   - API key paste  → debounced 500ms → POST /api/settings/api-key/test
//                       → on valid: POST /api/settings/api-key (saves +
//                         flips byok_enabled=true server-side).
//
// No "Save" button anywhere. Status banner reflects the engine's
// actual active path, not the UI's last selection.
//
// updateDirectorPauseStatus is called from main.js's 10Hz state push;
// it mutates store fields that drive the paused pill, marathon
// countdown, and extras visibility. Alpine reactivity through 10Hz
// proxy sets is fine here — most ticks no-op because the values
// don't change.

const VALIDATE_DEBOUNCE_MS = 500;
const DEFAULT_MODEL = 'claude-haiku-4-5-20251001';

let validateTimer = null;
let modelSaveTimer = null;

function storeDefinition() {
    return {
        // --- Server config snapshot ---
        enabled: false,
        byokEnabled: false,
        hasApiKey: false,
        apiKeyMasked: '',
        modelOnDisk: '',
        activePath: '',

        // --- Banner ---
        bannerState: 'loading',
        bannerText: 'Loading…',

        // --- BYOK input form ---
        byokInputValue: '',
        byokInputType: 'password',
        byokStatusKind: '',
        byokStatusText: '',
        get byokPlaceholder() {
            return this.hasApiKey && this.apiKeyMasked
                ? 'Replace key (paste new sk-ant-…)'
                : 'sk-ant-…';
        },

        // --- Model override input ---
        modelInputValue: '',
        modelStatusKind: '',
        modelStatusText: '',

        // --- Paused pill (10Hz) ---
        pausedReason: '',
        showPausedPill: false,
        get pausedReasonText() {
            if (this.pausedReason === 'idle') return 'idle';
            if (this.pausedReason === 'quota') return 'daily quota reached';
            return this.pausedReason;
        },

        // --- Marathon (10Hz) ---
        marathonActive: false,
        marathonRemainingText: '12h 0m',
        get showMarathonBtn() { return !this.marathonActive; },
        get showMarathonActive() { return this.marathonActive; },

        // --- Idle threshold ---
        idleThresholdSeconds: 5400,
        idleThresholdEnvOverride: null,
        idleThresholdHint: 'Pauses Lumi if the dashboard sees no activity for this long. Click anything to wake her up. Lights keep working — only AI calls pause.',

        // --- Derived visibility flags ---
        get showCards() { return this.enabled; },
        get showByokFields() { return this.byokEnabled; },
        get showByokCurrent() { return this.hasApiKey && !!this.apiKeyMasked; },
        get showExtras() { return this.enabled; },
        get isRelayMode() { return !this.byokEnabled; },
        get isByokMode() { return this.byokEnabled; },

        applyState(s) {
            this.enabled = !!s.enabled;
            this.byokEnabled = !!s.byok_enabled;
            this.hasApiKey = !!s.has_api_key;
            this.apiKeyMasked = s.api_key_masked || '';
            this.modelOnDisk = (s.model || '').trim();
            this.activePath = s.active_path || '';

            // Model override input — show non-default values, blank for default.
            // Don't stomp user's in-progress typing during fetchState roundtrip.
            const display = this.modelOnDisk && this.modelOnDisk !== DEFAULT_MODEL
                ? this.modelOnDisk : '';
            const modelEl = document.getElementById('director-byok-model');
            if (modelEl !== document.activeElement) {
                this.modelInputValue = display;
            }

            // Banner reflects what the engine is actually using.
            if (!this.enabled) {
                this.bannerState = 'off';
                this.bannerText = 'Lumi is off';
            } else if (this.activePath === 'byok') {
                this.bannerState = 'byok';
                this.bannerText = 'Lumi is listening · your Anthropic key';
            } else if (this.activePath === 'relay') {
                this.bannerState = 'relay';
                this.bannerText = 'Lumi is listening · Conductør AI';
            } else if (this.activePath === 'unavailable') {
                this.bannerState = 'warn';
                this.bannerText = 'Pro / Venue license required to run Conductør AI';
            } else {
                this.bannerState = 'off';
                this.bannerText = 'Lumi is off';
            }
        },

        async fetchState() {
            try {
                const r = await fetch('/api/director/config');
                if (!r.ok) throw new Error(`HTTP ${r.status}`);
                const data = await r.json();
                this.applyState(data);
            } catch (e) {
                this.bannerState = 'err';
                this.bannerText = `Failed to load: ${e.message || e}`;
            }
        },

        async persist(updates) {
            try {
                const r = await fetch('/api/director/config', {
                    method: 'PUT',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify(updates),
                });
                if (!r.ok) {
                    const err = await r.json().catch(() => ({}));
                    console.warn('Director save failed:', err);
                }
            } catch (e) {
                console.warn('Director save error:', e);
            } finally {
                await this.fetchState();
            }
        },

        toggleEnable(checked) {
            this.persist({enabled: !!checked});
        },

        selectMode(mode) {
            this.persist({byok_enabled: mode === 'byok'});
        },

        setByokStatus(kind, text) {
            this.byokStatusKind = kind || '';
            this.byokStatusText = text || '';
        },

        onByokInput() {
            clearTimeout(validateTimer);
            const val = (this.byokInputValue || '').trim();
            if (!val) {
                this.setByokStatus('', '');
                return;
            }
            if (!val.startsWith('sk-ant-') || val.length < 30) {
                this.setByokStatus('pending', "Doesn't look complete yet…");
                return;
            }
            this.setByokStatus('checking', 'Checking…');
            validateTimer = setTimeout(() => this.validateAndSave(val), VALIDATE_DEBOUNCE_MS);
        },

        async validateAndSave(key) {
            try {
                const testResp = await fetch('/api/settings/api-key/test', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({key}),
                });
                const test = await testResp.json();
                if (!test.valid) {
                    this.setByokStatus('err', `Invalid: ${test.error || 'unknown'}`);
                    return;
                }
                // Valid — persist. The save endpoint flips byok_enabled=true
                // server-side as a side effect, so the UI doesn't have to.
                const saveResp = await fetch('/api/settings/api-key', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({key}),
                });
                if (!saveResp.ok) {
                    const err = await saveResp.json().catch(() => ({}));
                    this.setByokStatus('err', `Save failed: ${err.error || 'unknown'}`);
                    return;
                }
                this.setByokStatus('ok', '✓ Valid — saved');
                this.byokInputValue = '';
                await this.fetchState();
            } catch (e) {
                this.setByokStatus('err', `Network error: ${e.message || e}`);
            }
        },

        async clearByokKey() {
            if (!confirm('Clear the saved Anthropic API key from this dashboard?')) return;
            try {
                await fetch('/api/settings/api-key', {method: 'DELETE'});
                this.setByokStatus('', '');
                // Clearing the key implies "go back to relay" — server-side
                // doesn't flip byok_enabled itself on DELETE; the engine
                // just falls through to relay since no key is present.
                await this.fetchState();
            } catch (e) {
                this.setByokStatus('err', `Failed to clear: ${e.message || e}`);
            }
        },

        toggleByokVisibility() {
            this.byokInputType = this.byokInputType === 'password' ? 'text' : 'password';
        },

        setModelStatus(kind, text) {
            this.modelStatusKind = kind || '';
            this.modelStatusText = text || '';
        },

        onModelInput() {
            clearTimeout(modelSaveTimer);
            const val = (this.modelInputValue || '').trim();
            const target = val || DEFAULT_MODEL;
            if (target === (this.modelOnDisk || DEFAULT_MODEL)) {
                this.setModelStatus('', '');
                return;
            }
            this.setModelStatus('checking', 'Saving…');
            modelSaveTimer = setTimeout(async () => {
                try {
                    const r = await fetch('/api/director/config', {
                        method: 'PUT',
                        headers: {'Content-Type': 'application/json'},
                        body: JSON.stringify({model: val}),  // empty = default on server
                    });
                    if (!r.ok) {
                        const err = await r.json().catch(() => ({}));
                        this.setModelStatus('err', err.error || 'Save failed');
                        return;
                    }
                    this.setModelStatus('ok', val ? '✓ Saved' : '✓ Default');
                    this.modelOnDisk = val || DEFAULT_MODEL;
                } catch (e) {
                    this.setModelStatus('err', `Network error: ${e.message || e}`);
                }
            }, VALIDATE_DEBOUNCE_MS);
        },

        // KL-101: idle threshold + marathon
        async fetchIdleThreshold() {
            try {
                const r = await fetch('/api/director/idle-threshold');
                const data = await r.json();
                this.idleThresholdSeconds = Number(data.seconds);
                this.idleThresholdEnvOverride = data.env_override;
                if (data.env_override !== null) {
                    // QA mode — show the override is active so a tester
                    // doesn't get confused by the dropdown not "doing
                    // anything." :disabled binding handles the visual.
                    this.idleThresholdHint = `CONDUCTOR_IDLE_THRESHOLD_SECONDS=${data.env_override} env override is active. Unset to use the saved setting.`;
                }
            } catch (_) { /* ignore */ }
        },

        saveIdleThreshold(seconds) {
            this.idleThresholdSeconds = parseInt(seconds, 10);
            fetch('/api/director/idle-threshold', {
                method: 'PUT',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({seconds: this.idleThresholdSeconds}),
            }).catch(() => {});
        },

        toggleMarathon(enabled) {
            fetch('/api/director/marathon', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({enabled: !!enabled}),
            }).catch(() => {});
        },

        resumeFromPause() {
            fetch('/api/director/resume', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: '{}',
            }).catch(() => {});
        },

        // 10Hz state push handler — keeps paused pill and marathon
        // countdown live without re-fetching /api/director/config.
        // Called from main.js socket.on('state_update').
        updatePauseStatus(director) {
            if (!director) return;
            // Mirror enabled so the extras visibility stays in sync without
            // waiting on a full fetchState roundtrip after toggles.
            this.enabled = !!director.enabled;

            const reason = director.paused_reason || '';
            this.pausedReason = reason;
            this.showPausedPill = !!reason;

            const until = Number(director.marathon_until || 0);
            const nowSec = Date.now() / 1000;
            const isActive = until > nowSec;
            this.marathonActive = isActive;
            if (isActive) {
                const remainingSec = Math.max(0, until - nowSec);
                const hours = Math.floor(remainingSec / 3600);
                const mins = Math.floor((remainingSec % 3600) / 60);
                this.marathonRemainingText = `${hours}h ${mins}m`;
            }
        },
    };
}

function ensureStore() {
    if (!window.Alpine) return null;
    if (!window.Alpine.store('director')) {
        window.Alpine.store('director', storeDefinition());
    }
    return window.Alpine.store('director');
}

if (window.Alpine) {
    ensureStore();
} else {
    document.addEventListener('alpine:init', ensureStore);
}

export function initDirectorConfig() {
    const panel = document.querySelector('[data-tab-panel="director"]');
    if (!panel) return;

    let store = ensureStore();
    if (!store) {
        document.addEventListener('alpine:init', () => {
            const s = ensureStore();
            if (s) {
                s.fetchState();
                s.fetchIdleThreshold();
            }
        }, {once: true});
        return;
    }
    store.fetchState();
    store.fetchIdleThreshold();
}

export function updateDirectorPauseStatus(director) {
    window.Alpine?.store('director')?.updatePauseStatus(director);
}
