// Help-tab feedback modal (KL-139 Alpine conversion).
//
// Structured form, draft persistence, relay-first submission with
// mailto fallback. Lives inside the SPA shell so it doesn't break the
// dashboard navigation. The about-tab jump links and "For support"
// diagnostics-copy buttons stay imperative — they're tab-level
// integrations, not modal state.

const DRAFT_KEY = 'conductor_feedback_draft_v3';
const HELP_EMAIL = 'help@conductor.lighting';
const RELAY_FEEDBACK_URL = 'https://relay.conductor.lighting/feedback';
const SUBMIT_TIMEOUT_MS = 8000;

// activity/audio dropdowns store the value attribute, not the label.
// Keep label maps so the email body shows "Setup / first run" instead
// of "setup".
const ACTIVITY_LABELS = {
    setup: 'Setup / first run',
    fixtures: 'Configuring fixtures',
    govee: 'Configuring Govee',
    live: 'Running a live show',
    other: 'Something else',
};
const AUDIO_LABELS = {
    loopback: 'Loopback (BlackHole / VB-Cable)',
    mic: 'Microphone',
    line: 'Line in',
    midi: 'MIDI only',
    unsure: 'Not sure',
};

async function fetchDiagnostics() {
    try {
        const r = await fetch('/api/help/diagnostics');
        if (!r.ok) return null;
        return await r.json();
    } catch (_) { return null; }
}

function diagBlockLines(d) {
    if (!d) return ['', '-- diagnostics: unavailable --'];
    const cs = d.config_summary || {};
    const head = [
        '',
        '-- diagnostics (auto-attached) --',
        `Conductor v${d.version || '?'}`,
        `OS: ${d.os || '?'} (${d.arch || '?'})`,
        `Tier: ${cs.tier || '?'}`,
        `Fixtures: ${cs.fixture_count ?? 0}, Govee: ${cs.govee_count ?? 0}, DMX node: ${cs.dmx_present ? 'yes' : 'no'}`,
        `Uptime: ${d.uptime_seconds ?? 0}s`,
        `Anon ID: ${d.anon_id || '(none)'}`,
        '',
        '-- recent log --',
    ];
    const tail = Array.isArray(d.log_tail) ? d.log_tail : [];
    return head.concat(tail.length ? tail : ['(empty)']);
}

function buildEmailBody(payload) {
    const {type, title, email, wantsCredit, creditAs, steps, expected, actual,
           details, severity, activity, audio, diag} = payload;
    const lines = ['Type: ' + type, 'Summary: ' + title, ''];
    if (type === 'bug') {
        lines.push('Severity: ' + (severity || '(not set)'));
        lines.push('');
        lines.push('Steps to reproduce:');
        lines.push(steps || '(not provided)');
        lines.push('');
        lines.push('Expected:');
        lines.push(expected || '(not provided)');
        lines.push('');
        lines.push('Actual:');
        lines.push(actual || '(not provided)');
    } else {
        lines.push('Details:');
        lines.push(details || '(not provided)');
    }
    lines.push('');
    if (activity) lines.push('What you were doing: ' + activity);
    if (audio) lines.push('Audio source: ' + audio);
    lines.push('');
    lines.push('Reply to: ' + (email || 'not provided'));
    lines.push('Credit on release: ' + (wantsCredit ? 'yes' : 'no'));
    if (wantsCredit && creditAs) lines.push('Credit as: ' + creditAs);
    diagBlockLines(diag).forEach(l => lines.push(l));
    return lines.join('\n');
}

async function postToRelay(payload, diag) {
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), SUBMIT_TIMEOUT_MS);
    try {
        const r = await fetch(RELAY_FEEDBACK_URL, {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({
                type: payload.type,
                title: payload.title,
                severity: payload.severity || null,
                steps: payload.steps || null,
                expected: payload.expected || null,
                actual: payload.actual || null,
                details: payload.details || null,
                activity: payload.activity || null,
                audio_source: payload.audio || null,
                email: payload.email || null,
                credit_me: !!payload.wantsCredit,
                credit_name: payload.wantsCredit ? (payload.creditAs || null) : null,
                diagnostics: diag || null,
            }),
            signal: controller.signal,
        });
        clearTimeout(timer);
        // Always try to parse the body so callers can read the relay's
        // structured error code (e.g. {error: "rate_limited"} on 429)
        // and route the UX accordingly.
        let data = null;
        try { data = await r.json(); } catch (_) { /* non-JSON response */ }
        if (!r.ok) return {ok: false, status: r.status, data};
        return {ok: true, data};
    } catch (e) {
        clearTimeout(timer);
        return {ok: false, status: 0, error: e};
    }
}

function storeDefinition() {
    return {
        // --- Modal state ---
        shown: false,
        submitted: false,
        submitBusy: false,
        submitLabel: 'Submit ticket',

        // --- Form fields ---
        type: 'bug',
        title: '',
        severity: 'unexpected',
        steps: '',
        expected: '',
        actual: '',
        details: '',
        activity: '',
        audio: '',
        email: '',
        creditMe: false,
        creditName: '',

        // --- Result banner ---
        showResult: false,
        resultKind: '',
        resultHtml: '',

        // --- Computed ---
        get formClass() {
            const classes = [];
            if (this.type === 'bug') classes.push('is-bug');
            if (this.submitted) classes.push('is-success');
            return classes.join(' ');
        },
        get isBug() { return this.type === 'bug'; },

        open() {
            this.shown = true;
            // Reset post-submit state so a previous successful submit
            // doesn't lock the modal on next open.
            this.submitted = false;
            this.showResult = false;
            this.resultKind = '';
            this.resultHtml = '';
            this.submitBusy = false;
            this.submitLabel = 'Submit ticket';
            setTimeout(() => document.getElementById('fb-title')?.focus(), 0);
        },

        close() {
            this.shown = false;
        },

        saveDraft() {
            const draft = {
                type: this.type,
                title: this.title,
                severity: this.severity,
                steps: this.steps,
                expected: this.expected,
                actual: this.actual,
                details: this.details,
                activity: this.activity,
                audio: this.audio,
                email: this.email,
                credit_me: this.creditMe,
                credit_name: this.creditName,
            };
            try { localStorage.setItem(DRAFT_KEY, JSON.stringify(draft)); } catch (_) {}
        },

        restoreDraft() {
            let draft;
            try {
                draft = JSON.parse(localStorage.getItem(DRAFT_KEY) || '{}');
            } catch (_) { return; }
            if (typeof draft !== 'object' || !draft) return;
            if (draft.type) this.type = draft.type;
            if (typeof draft.title === 'string') this.title = draft.title;
            if (draft.severity) this.severity = draft.severity;
            if (typeof draft.steps === 'string') this.steps = draft.steps;
            if (typeof draft.expected === 'string') this.expected = draft.expected;
            if (typeof draft.actual === 'string') this.actual = draft.actual;
            if (typeof draft.details === 'string') this.details = draft.details;
            if (typeof draft.activity === 'string') this.activity = draft.activity;
            if (typeof draft.audio === 'string') this.audio = draft.audio;
            if (typeof draft.email === 'string') this.email = draft.email;
            this.creditMe = !!draft.credit_me;
            if (typeof draft.credit_name === 'string') this.creditName = draft.credit_name;
        },

        clearDraft() {
            try { localStorage.removeItem(DRAFT_KEY); } catch (_) {}
        },

        lockAfterSubmit() {
            this.submitted = true;
            this.submitBusy = false;
        },

        async submit() {
            if (!this.title.trim()) {
                document.getElementById('feedback-form')?.reportValidity();
                return;
            }
            this.submitBusy = true;
            this.submitLabel = 'Submitting…';
            this.showResult = false;

            const diag = await fetchDiagnostics();
            const payload = {
                type: this.type,
                title: this.title.trim(),
                email: this.email.trim(),
                wantsCredit: this.creditMe,
                creditAs: this.creditName.trim(),
                steps: this.steps.trim(),
                expected: this.expected.trim(),
                actual: this.actual.trim(),
                details: this.details.trim(),
                severity: this.severity,
                activity: ACTIVITY_LABELS[this.activity] || '',
                audio: AUDIO_LABELS[this.audio] || '',
                diag,
            };

            const relayResp = await postToRelay(payload, diag);

            if (relayResp.ok && relayResp.data && relayResp.data.ticket_id) {
                const id = relayResp.data.ticket_id;
                const url = relayResp.data.ticket_url || '#';
                this.resultKind = 'ok';
                this.resultHtml = `Submitted as <a href="${url}" target="_blank" rel="noopener noreferrer"><strong>${id}</strong></a>. Thanks for the report!`;
                this.showResult = true;
                this.clearDraft();
                // Reset only the volatile fields; keep email + credit prefs
                // so a subsequent open prefills with the user's identity.
                this.title = '';
                this.steps = '';
                this.expected = '';
                this.actual = '';
                this.details = '';
                this.activity = '';
                this.audio = '';
                this.lockAfterSubmit();
            } else if (relayResp.status === 429) {
                // KL-141: rate-limited. The relay returns {error: "rate_limited"}
                // for the per-minute cap and {error: "daily_limit"} for the
                // 24h cap. Surface inline — do NOT fall through to mailto
                // (which would just shift spam to help@conductor.lighting),
                // and don't lock the modal so the user can retry later. The
                // draft persists so they don't have to retype.
                const code = relayResp.data?.error || '';
                this.resultKind = 'err';
                this.resultHtml = code === 'daily_limit'
                    ? "You've reached today's feedback limit. Please come back tomorrow — your draft is saved."
                    : "You've sent feedback recently — please wait a minute and try again.";
                this.showResult = true;
                this.submitBusy = false;
                this.submitLabel = 'Submit ticket';
            } else {
                // Network / 5xx / other 4xx — fall back to mailto so the
                // report doesn't get lost. 429 was already handled above.
                const body = buildEmailBody(payload);
                const subject = '[Conductor ' + payload.type + '] ' + payload.title;
                const mailto = 'mailto:' + encodeURIComponent(HELP_EMAIL) +
                               '?subject=' + encodeURIComponent(subject) +
                               '&body=' + encodeURIComponent(body);
                window.location.href = mailto;
                this.resultKind = 'ok';
                this.resultHtml = 'Opened your email client as a fallback. Send the message to finish reporting.';
                this.showResult = true;
                this.clearDraft();
                this.lockAfterSubmit();
            }
        },
    };
}

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

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

// About-tab links (e.g. "Send great feedback.") that jump to another
// tab and prefer to land on a specific control. Piggy-backs on the
// existing tab-button click path so initTabs handles activation +
// telemetry consistently. data-open-feedback="true" additionally pops
// the feedback modal so the user lands ready to type.
function initAboutJumpLinks() {
    document.querySelectorAll('[data-jump-tab]').forEach(el => {
        if (el.dataset.aboutJumpBound === '1') return;
        if (el.closest('[data-tab-panel="about"]') === null) return;
        el.dataset.aboutJumpBound = '1';
        el.addEventListener('click', (e) => {
            e.preventDefault();
            const target = el.dataset.jumpTab;
            document.getElementById(`tab-btn-${target}`)?.click();
            if (el.dataset.openFeedback === '1' || target === 'help') {
                document.getElementById('btn-open-feedback')?.click();
            }
        });
    });
}

// "For support" card on the Help tab — click-to-copy for static
// diagnostic IDs (app version, anonymous user ID). Values are
// server-rendered into <code> elements so we just wire the buttons
// here.
function initDiagnosticsCopy() {
    const buttons = document.querySelectorAll('.diagnostics-copy[data-copy-target]');
    buttons.forEach(btn => {
        btn.addEventListener('click', async () => {
            const targetId = btn.dataset.copyTarget;
            const target = document.getElementById(targetId);
            if (!target) return;
            const value = target.textContent.trim();
            if (!value || value === '—') return;
            try {
                await navigator.clipboard.writeText(value);
            } catch {
                // Older WebView fallback: select the value so the user
                // can hit the OS copy shortcut.
                const range = document.createRange();
                range.selectNodeContents(target);
                const sel = window.getSelection();
                sel.removeAllRanges();
                sel.addRange(range);
                return;
            }
            const original = btn.textContent;
            btn.textContent = 'Copied';
            btn.classList.add('copied');
            setTimeout(() => {
                btn.textContent = original;
                btn.classList.remove('copied');
            }, 1200);
        });
    });
}

export function initFeedback() {
    let store = ensureStore();
    if (!store) {
        document.addEventListener('alpine:init', () => {
            ensureStore()?.restoreDraft();
        }, {once: true});
    } else {
        store.restoreDraft();
    }

    // Document-level Escape closes the modal. Imperative because it's
    // a global keyboard shortcut, not a per-element binding.
    document.addEventListener('keydown', (e) => {
        if (e.key !== 'Escape') return;
        const s = window.Alpine?.store('feedback');
        if (s && s.shown) s.close();
    });

    initDiagnosticsCopy();
    initAboutJumpLinks();
}
