// Reader preferences. Stored server-side per user, with a localStorage
// shadow so anonymous browsers still get a consistent experience across
// page loads and so logged-in users don't flash defaults while the
// server response is in flight.
//
// Mutated client-side only — same pattern as session.svelte.ts. SSR
// always sees the post-construct defaults; `init()` is called from the
// root layout's onMount and reads localStorage / pulls the server row.

import {
    getPreferences,
    updatePreferences,
    type Preferences,
    type ReaderMode,
    type ReaderPageGap
} from './api/preferences';

const MODE_KEY = 'mangalord-reader-mode';
const GAP_KEY = 'mangalord-reader-gap';

const MODES: ReaderMode[] = ['single', 'continuous'];
const GAPS: ReaderPageGap[] = ['none', 'small', 'medium', 'large'];

function readStoredMode(): ReaderMode {
    if (typeof localStorage === 'undefined') return 'single';
    const v = localStorage.getItem(MODE_KEY);
    return (MODES as string[]).includes(v ?? '') ? (v as ReaderMode) : 'single';
}

function readStoredGap(): ReaderPageGap {
    if (typeof localStorage === 'undefined') return 'none';
    const v = localStorage.getItem(GAP_KEY);
    return (GAPS as string[]).includes(v ?? '') ? (v as ReaderPageGap) : 'none';
}

class PreferencesStore {
    readerMode: ReaderMode = $state('single');
    readerPageGap: ReaderPageGap = $state('none');
    loaded = $state(false);
    // Bumped before each server fetch so a slow response that resolves
    // after a newer one can't clobber the state.
    private seq = 0;

    /**
     * One-shot init: hydrate from localStorage (instant), then ask the
     * server (slow but authoritative). Safe to call multiple times — the
     * seq guard makes overlapping calls converge on the latest result.
     */
    async init(): Promise<void> {
        this.readerMode = readStoredMode();
        this.readerPageGap = readStoredGap();
        await this.refresh();
    }

    async refresh(): Promise<void> {
        if (typeof window === 'undefined') return;
        const seq = ++this.seq;
        try {
            const server = await getPreferences();
            if (seq !== this.seq) return;
            if (server) this.apply(server);
        } finally {
            if (seq === this.seq) this.loaded = true;
        }
    }

    /// Reset to defaults and clear the localStorage shadow. Called on
    /// logout so user A's settings don't follow user B (or an anonymous
    /// browser) on a shared device.
    clearForLogout(): void {
        this.seq++;
        this.readerMode = 'single';
        this.readerPageGap = 'none';
        if (typeof localStorage !== 'undefined') {
            localStorage.removeItem(MODE_KEY);
            localStorage.removeItem(GAP_KEY);
        }
    }

    setMode(mode: ReaderMode): void {
        const prev = this.readerMode;
        this.readerMode = mode;
        if (typeof localStorage !== 'undefined') localStorage.setItem(MODE_KEY, mode);
        this.pushToServer({ reader_mode: mode }, () => {
            this.readerMode = prev;
            if (typeof localStorage !== 'undefined') localStorage.setItem(MODE_KEY, prev);
        });
    }

    setGap(gap: ReaderPageGap): void {
        const prev = this.readerPageGap;
        this.readerPageGap = gap;
        if (typeof localStorage !== 'undefined') localStorage.setItem(GAP_KEY, gap);
        this.pushToServer({ reader_page_gap: gap }, () => {
            this.readerPageGap = prev;
            if (typeof localStorage !== 'undefined') localStorage.setItem(GAP_KEY, prev);
        });
    }

    private apply(p: Preferences): void {
        // Validate against the allowlist so a future server-side value
        // we don't yet understand can't poison the UI (e.g. an unknown
        // gap would render `style:gap="undefinedpx"` and break layout).
        const mode = (MODES as string[]).includes(p.reader_mode)
            ? (p.reader_mode as ReaderMode)
            : this.readerMode;
        const gap = (GAPS as string[]).includes(p.reader_page_gap)
            ? (p.reader_page_gap as ReaderPageGap)
            : this.readerPageGap;
        this.readerMode = mode;
        this.readerPageGap = gap;
        if (typeof localStorage !== 'undefined') {
            localStorage.setItem(MODE_KEY, mode);
            localStorage.setItem(GAP_KEY, gap);
        }
    }

    private pushToServer(
        patch: Partial<Pick<Preferences, 'reader_mode' | 'reader_page_gap'>>,
        revert: () => void
    ): void {
        // Optimistic local update already happened. 401 (anonymous user)
        // is the expected path for guests — the localStorage write keeps
        // their choice. Any other failure (network, 5xx) means the
        // server doesn't agree with our local state, so roll the
        // optimistic change back.
        updatePreferences(patch).catch((e) => {
            const status = (e as { status?: number })?.status;
            if (status === 401) return;
            console.error('updatePreferences failed', e);
            revert();
        });
    }
}

export const preferences = new PreferencesStore();
