import Alpine from 'alpinejs';

/* ============================================================
   Helpers
   ============================================================ */
const prefersReduced = () =>
    window.matchMedia('(prefers-reduced-motion: reduce)').matches;

function shuffle(arr) {
    const a = [...arr];
    for (let i = a.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [a[i], a[j]] = [a[j], a[i]];
    }
    return a;
}

function normalize(s) {
    return (s || '')
        .toLowerCase()
        .normalize('NFD')
        .replace(/[̀-ͯ]/g, '')
        .replace(/[^a-z0-9\s]/g, '')
        .trim();
}

/** Fuzzy similarity 0..1 — ported from the original OpenMode logic. */
function checkOpenAnswer(userAnswer, correctAnswer) {
    const a = normalize(userAnswer);
    const b = normalize(correctAnswer);
    if (!a) return 0;
    if (a === b) return 1;
    if (b.includes(a) && a.length > 5) return 0.8;
    const aWords = new Set(a.split(/\s+/).filter((w) => w.length > 3));
    const bWords = new Set(b.split(/\s+/).filter((w) => w.length > 3));
    const inter = [...aWords].filter((w) => bWords.has(w));
    if (bWords.size === 0) return 0;
    return Math.min(inter.length / bWords.size, 1);
}

async function postProgress(payload) {
    try {
        const csrf = document.querySelector('meta[name="csrf-token"]')?.content;
        const url = document.querySelector('meta[name="progress-url"]')?.content;
        if (!url) return;
        await fetch(url, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                Accept: 'application/json',
                'X-CSRF-TOKEN': csrf,
            },
            body: JSON.stringify(payload),
        });
    } catch (e) {
        /* progress is best-effort */
    }
}

/** Lightweight canvas confetti burst. */
function confetti() {
    if (prefersReduced()) return;
    const canvas = document.createElement('canvas');
    canvas.style.cssText =
        'position:fixed;inset:0;pointer-events:none;z-index:9999';
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    document.body.appendChild(canvas);
    const ctx = canvas.getContext('2d');
    const colors = ['#8b5cf6', '#22d3ee', '#fbbf24', '#34d399', '#fb7185'];
    const pieces = Array.from({ length: 130 }, () => ({
        x: canvas.width / 2 + (Math.random() - 0.5) * 240,
        y: canvas.height / 3,
        vx: (Math.random() - 0.5) * 12,
        vy: Math.random() * -14 - 4,
        size: Math.random() * 8 + 4,
        color: colors[(Math.random() * colors.length) | 0],
        rot: Math.random() * Math.PI,
        vr: (Math.random() - 0.5) * 0.3,
    }));
    let frame = 0;
    (function loop() {
        frame++;
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        pieces.forEach((p) => {
            p.vy += 0.35;
            p.x += p.vx;
            p.y += p.vy;
            p.rot += p.vr;
            ctx.save();
            ctx.translate(p.x, p.y);
            ctx.rotate(p.rot);
            ctx.fillStyle = p.color;
            ctx.fillRect(-p.size / 2, -p.size / 2, p.size, p.size * 0.6);
            ctx.restore();
        });
        if (frame < 130) requestAnimationFrame(loop);
        else canvas.remove();
    })();
}

window.SF = { shuffle, normalize, checkOpenAnswer, postProgress, confetti };

/* ============================================================
   Mobile nav
   ============================================================ */
Alpine.data('navMenu', () => ({
    open: false,
    toggle() {
        this.open = !this.open;
    },
}));

/* ============================================================
   Flashcards mode
   ============================================================ */
Alpine.data('flashcards', (deck, ctx) => ({
    ctx,
    cards: [],
    index: 0,
    flipped: false,
    answered: false,
    saving: false,
    done: false,
    results: { correct: 0, incorrect: 0 },

    init() {
        this.cards = shuffle(deck);
        this._key = (e) => this.onKey(e);
        window.addEventListener('keydown', this._key);
    },
    destroy() {
        window.removeEventListener('keydown', this._key);
    },

    get current() {
        return this.cards[this.index];
    },
    get total() {
        return this.cards.length;
    },
    get progress() {
        return this.total ? Math.round((this.index / this.total) * 100) : 0;
    },
    get percent() {
        const t = this.results.correct + this.results.incorrect;
        return t ? Math.round((this.results.correct / t) * 100) : 0;
    },

    flip() {
        if (!this.answered) this.flipped = !this.flipped;
    },

    async answer(result) {
        if (this.answered || !this.flipped) return;
        this.answered = true;
        this.results[result]++;
        this.saving = true;
        await postProgress({
            card_id: this.current.id,
            course_id: this.ctx.courseId,
            chapter_id: this.current.chapter_id,
            result,
            mode: 'flashcards',
        });
        this.saving = false;
        setTimeout(() => {
            if (this.index + 1 >= this.total) {
                this.done = true;
                if (this.percent >= 60) confetti();
            } else {
                this.index++;
                this.flipped = false;
                this.answered = false;
            }
        }, 260);
    },

    restart() {
        this.cards = shuffle(deck);
        this.index = 0;
        this.flipped = false;
        this.answered = false;
        this.done = false;
        this.results = { correct: 0, incorrect: 0 };
    },

    onKey(e) {
        if (this.done) return;
        if (e.code === 'Space') {
            e.preventDefault();
            this.flip();
        }
        if (this.flipped && !this.answered) {
            if (e.code === 'ArrowRight' || e.code === 'KeyJ') this.answer('correct');
            if (e.code === 'ArrowLeft' || e.code === 'KeyF') this.answer('incorrect');
        }
    },
}));

/* ============================================================
   QCM mode
   ============================================================ */
Alpine.data('qcm', (questions, ctx) => ({
    ctx,
    questions,
    index: 0,
    selected: null,
    done: false,
    saving: false,
    results: { correct: 0, incorrect: 0 },

    init() {
        this._key = (e) => this.onKey(e);
        window.addEventListener('keydown', this._key);
    },
    destroy() {
        window.removeEventListener('keydown', this._key);
    },

    get current() {
        return this.questions[this.index];
    },
    get total() {
        return this.questions.length;
    },
    get progress() {
        return this.total ? Math.round((this.index / this.total) * 100) : 0;
    },
    get percent() {
        const t = this.results.correct + this.results.incorrect;
        return t ? Math.round((this.results.correct / t) * 100) : 0;
    },
    optionClass(option) {
        if (!this.selected) return '';
        if (option === this.current.correct) return 'is-correct';
        if (option === this.selected) return 'is-wrong';
        return '';
    },

    async select(option) {
        if (this.selected) return;
        this.selected = option;
        const result = option === this.current.correct ? 'correct' : 'incorrect';
        this.results[result]++;
        this.saving = true;
        await postProgress({
            card_id: this.current.card_id,
            course_id: this.ctx.courseId,
            chapter_id: this.ctx.chapterId,
            result,
            mode: 'qcm',
        });
        this.saving = false;
    },

    next() {
        if (this.index + 1 >= this.total) {
            this.done = true;
            if (this.percent >= 60) confetti();
        } else {
            this.index++;
            this.selected = null;
        }
    },

    restart() {
        this.index = 0;
        this.selected = null;
        this.done = false;
        this.results = { correct: 0, incorrect: 0 };
    },

    onKey(e) {
        if (this.done) return;
        if (!this.selected && ['Digit1', 'Digit2', 'Digit3', 'Digit4'].includes(e.code)) {
            const i = Number(e.code.slice(-1)) - 1;
            if (this.current.options[i] !== undefined) this.select(this.current.options[i]);
        } else if (this.selected && (e.code === 'Enter' || e.code === 'ArrowRight')) {
            this.next();
        }
    },
}));

/* ============================================================
   Open answer mode
   ============================================================ */
Alpine.data('openAnswer', (deck, ctx) => ({
    ctx,
    cards: [],
    index: 0,
    answer: '',
    score: null,
    done: false,
    results: { correct: 0, partial: 0, incorrect: 0 },

    init() {
        this.cards = shuffle(deck).slice(0, 15);
    },

    get current() {
        return this.cards[this.index];
    },
    get total() {
        return this.cards.length;
    },
    get progress() {
        return this.total ? Math.round((this.index / this.total) * 100) : 0;
    },
    get scorePercent() {
        return this.score === null ? 0 : Math.round(this.score * 100);
    },
    get finalPercent() {
        const t = this.results.correct + this.results.partial + this.results.incorrect;
        return t ? Math.round(((this.results.correct + this.results.partial * 0.5) / t) * 100) : 0;
    },
    get verdict() {
        if (this.score === null) return null;
        if (this.score >= 0.75) return { label: 'Excellent ✨', tone: 'success' };
        if (this.score >= 0.4) return { label: 'Assez bien 👍', tone: 'gold' };
        return { label: 'À revoir 📖', tone: 'danger' };
    },

    async submit() {
        if (!this.answer.trim() || this.score !== null) return;
        const sim = checkOpenAnswer(this.answer, this.current.definition);
        this.score = sim;
        let result;
        if (sim >= 0.75) result = 'correct';
        else if (sim >= 0.4) result = 'partial';
        else result = 'incorrect';
        this.results[result]++;
        await postProgress({
            card_id: this.current.id,
            course_id: this.ctx.courseId,
            chapter_id: this.current.chapter_id,
            result,
            mode: 'open',
        });
    },

    next() {
        if (this.index + 1 >= this.total) {
            this.done = true;
            if (this.finalPercent >= 60) confetti();
        } else {
            this.index++;
            this.answer = '';
            this.score = null;
        }
    },

    restart() {
        this.cards = shuffle(deck).slice(0, 15);
        this.index = 0;
        this.answer = '';
        this.score = null;
        this.done = false;
        this.results = { correct: 0, partial: 0, incorrect: 0 };
    },
}));

/* ============================================================
   Infinite mode — wrong cards come back, mastered ones leave.
   ============================================================ */
Alpine.data('infinite', (deck, ctx) => ({
    ctx,
    queue: [],
    masteredIds: [],
    wrongIds: [],
    flipped: false,
    answered: false,
    lastResult: null,
    saving: false,
    done: false,
    totalAnswered: 0,
    totalCorrect: 0,
    totalCards: 0,
    startedAt: 0,
    elapsed: '0:00',

    init() {
        this.totalCards = deck.length;
        this.queue = shuffle(deck).map((card) => ({ card, timesWrong: 0 }));
        this.startedAt = Date.now();
        this._key = (e) => this.onKey(e);
        window.addEventListener('keydown', this._key);
    },
    destroy() {
        window.removeEventListener('keydown', this._key);
    },

    get currentItem() {
        return this.queue[0];
    },
    get current() {
        return this.currentItem?.card;
    },
    get masteredCount() {
        return this.masteredIds.length;
    },
    get remainingCount() {
        return this.queue.length;
    },
    get progress() {
        return this.totalCards ? Math.round((this.masteredCount / this.totalCards) * 100) : 0;
    },
    get sessionPercent() {
        return this.totalAnswered ? Math.round((this.totalCorrect / this.totalAnswered) * 100) : 0;
    },
    cellColor(id) {
        if (this.masteredIds.includes(id)) return 'var(--color-success)';
        if (this.queue.some((q) => q.card.id === id && q.timesWrong > 0)) return 'var(--color-warning)';
        return 'rgba(255,255,255,.1)';
    },

    flip() {
        if (!this.answered) this.flipped = !this.flipped;
    },

    async answer(result) {
        if (this.answered || !this.flipped || !this.current) return;
        this.answered = true;
        this.lastResult = result;
        this.totalAnswered++;
        if (result === 'correct') this.totalCorrect++;
        this.saving = true;
        await postProgress({
            card_id: this.current.id,
            course_id: this.ctx.courseId,
            chapter_id: this.current.chapter_id,
            result,
            mode: 'infinite',
        });
        this.saving = false;
    },

    advance() {
        if (!this.currentItem) return;
        const [head, ...rest] = this.queue;
        if (this.lastResult === 'correct') {
            this.masteredIds.push(head.card.id);
            if (rest.length === 0) {
                this.queue = [];
                this.finish();
                return;
            }
            this.queue = rest;
        } else {
            const updated = { ...head, timesWrong: head.timesWrong + 1 };
            const insertMin = Math.min(2, rest.length);
            const insertMax = Math.min(rest.length, Math.max(3, Math.floor(rest.length * 0.3)));
            const at = insertMin + Math.floor(Math.random() * (insertMax - insertMin + 1));
            const q = [...rest];
            q.splice(at, 0, updated);
            this.queue = q;
        }
        this.flipped = false;
        this.answered = false;
        this.lastResult = null;
    },

    finish() {
        const secs = Math.round((Date.now() - this.startedAt) / 1000);
        this.elapsed = `${Math.floor(secs / 60)}:${String(secs % 60).padStart(2, '0')}`;
        this.done = true;
        confetti();
    },

    restart() {
        this.queue = shuffle(deck).map((card) => ({ card, timesWrong: 0 }));
        this.masteredIds = [];
        this.flipped = false;
        this.answered = false;
        this.lastResult = null;
        this.done = false;
        this.totalAnswered = 0;
        this.totalCorrect = 0;
        this.startedAt = Date.now();
    },

    onKey(e) {
        if (this.done) return;
        if (e.code === 'Space') {
            e.preventDefault();
            this.flip();
        }
        if (this.flipped && !this.answered) {
            if (e.code === 'ArrowRight' || e.code === 'KeyJ') this.answer('correct');
            if (e.code === 'ArrowLeft' || e.code === 'KeyF') this.answer('incorrect');
        }
        if (this.answered && (e.code === 'ArrowRight' || e.code === 'Enter')) this.advance();
    },
}));

window.Alpine = Alpine;
Alpine.start();
