/**
 * Local-storage facade used by:
 *  - Level unlock state (req 17.1)
 *  - Floating control layout (req 17.2)
 *  - BGM / SFX volume (req 17.3, 16.4)
 *  - Tutorial completion flags (req 17.4)
 *  - Story-intro seen flag (req 17.5 / 19.5)
 *
 * Rationale for the thin facade:
 *  - The WeChat Mini Game runtime exposes `wx.setStorageSync` while the
 *    in-editor / browser preview exposes `sys.localStorage`. We isolate
 *    both behind a single `IStorageDriver` interface so that switching
 *    platforms is a single line.
 *  - On read, a failure never throws: it returns the provided default value
 *    (req 17.6 — "must not crash if local storage is unreadable").
 *  - All values go through JSON serialisation so that structured objects
 *    round-trip without callers having to remember to stringify.
 */

export interface IStorageDriver {
    getItem(key: string): string | null;
    setItem(key: string, value: string): void;
    removeItem(key: string): void;
}

/** Selects the best available driver at runtime. */
function detectDriver(): IStorageDriver {
    // 1. WeChat Mini Game global
    const wxGlobal = (globalThis as any).wx;
    if (wxGlobal && typeof wxGlobal.setStorageSync === 'function') {
        return {
            getItem(key) {
                try {
                    const v = wxGlobal.getStorageSync(key);
                    return v === '' ? null : (v as string);
                } catch {
                    return null;
                }
            },
            setItem(key, value) {
                try {
                    wxGlobal.setStorageSync(key, value);
                } catch {
                    // swallow — req 17.6
                }
            },
            removeItem(key) {
                try {
                    wxGlobal.removeStorageSync(key);
                } catch {
                    // swallow
                }
            },
        };
    }

    // 2. Browser localStorage
    if (typeof globalThis !== 'undefined' && (globalThis as any).localStorage) {
        const ls = (globalThis as any).localStorage as Storage;
        return {
            getItem: (k) => ls.getItem(k),
            setItem: (k, v) => ls.setItem(k, v),
            removeItem: (k) => ls.removeItem(k),
        };
    }

    // 3. In-memory fallback (Jest, Node-only unit tests).
    const mem = new Map<string, string>();
    return {
        getItem: (k) => (mem.has(k) ? (mem.get(k) as string) : null),
        setItem: (k, v) => {
            mem.set(k, v);
        },
        removeItem: (k) => {
            mem.delete(k);
        },
    };
}

export class StorageMgr {
    private driver: IStorageDriver;

    constructor(driver?: IStorageDriver) {
        this.driver = driver ?? detectDriver();
    }

    /**
     * Read a JSON-serialisable value. Returns `defaultValue` if the key is
     * missing, unparseable, or the underlying driver throws (req 17.6).
     */
    public get<T>(key: string, defaultValue: T): T {
        try {
            const raw = this.driver.getItem(key);
            if (raw == null) {
                return defaultValue;
            }
            return JSON.parse(raw) as T;
        } catch {
            return defaultValue;
        }
    }

    /** Write a JSON-serialisable value. Silently ignores driver errors. */
    public set<T>(key: string, value: T): void {
        try {
            this.driver.setItem(key, JSON.stringify(value));
        } catch {
            // req 17.6
        }
    }

    public remove(key: string): void {
        this.driver.removeItem(key);
    }

    /** Swap the driver at runtime. Used in unit tests and platform ports. */
    public setDriver(driver: IStorageDriver): void {
        this.driver = driver;
    }
}

/** Shared project-wide storage manager. */
export const globalStorageMgr = new StorageMgr();
