import { StorySceneCtrl, BASE_TYPING_CPS } from '@ui/StorySceneCtrl';
import { StorageMgr } from '@common/StorageMgr';
import { IStorySceneConfig } from '@data/Interfaces';
import { STORAGE_KEY } from '@common/Constants';

const SCENE: IStorySceneConfig = {
id: 'chapter_1_intro',
    bgm: 'bgm_story',
    maxDurationSec: 30,
    pages: [
        { index: 1, illustration: 'p1', text: 'A' },
        { index: 2, illustration: 'p2', text: 'BCDE' },
        { index: 3, illustration: 'p3', text: 'FGH' },
    ],
};

function mem() {
    const m = new Map<string, string>();
    return {
        getItem: (k: string) => (m.has(k) ? (m.get(k) as string) : null),
        setItem: (k: string, v: string) => {
            m.set(k, v);
        },
        removeItem: (k: string) => {
            m.delete(k);
        },
    };
}

describe('StorySceneCtrl — first-time gate (req 19.5)', () => {
    it('start() reports "already_seen" when storage flag is set', () => {
        const storage = new StorageMgr(mem());
        storage.set(STORAGE_KEY.StoryIntroSeen, true);
        const ctrl = new StorySceneCtrl(SCENE, storage);
        expect(ctrl.start()).toBe('already_seen');
    });

    it('start() reports "playing" on first run', () => {
        const ctrl = new StorySceneCtrl(SCENE, new StorageMgr(mem()));
        expect(ctrl.start()).toBe('playing');
        expect(ctrl.status).toBe('typing');
    });
});

describe('StorySceneCtrl — typewriter (req 19.2-19.3)', () => {
    it('reveals characters at BASE_TYPING_CPS', () => {
        const ctrl = new StorySceneCtrl(SCENE, new StorageMgr(mem()));
        ctrl.start();
        // Advance enough time to type "A".
        ctrl.tick(1 / BASE_TYPING_CPS + 0.001);
        expect(ctrl.visibleText).toBe('A');
        // Page "A" complete → waiting_next.
        expect(ctrl.status).toBe('waiting_next');
    });

    it('tap during typing fully reveals the current page', () => {
        const ctrl = new StorySceneCtrl(SCENE, new StorageMgr(mem()));
        ctrl.start();
        ctrl.onTap(); // accelerate page 1 ("A")
        expect(ctrl.visibleText).toBe('A');
        expect(ctrl.status).toBe('waiting_next');
        // Next tap → advance to page 2.
        ctrl.onTap();
        expect(ctrl.currentPageNumber).toBe(2);
        expect(ctrl.status).toBe('typing');
    });
});

describe('StorySceneCtrl — skip (req 19.4)', () => {
    it('onSkip immediately finishes and marks seen', () => {
        const storage = new StorageMgr(mem());
        const onFinished = jest.fn();
        const ctrl = new StorySceneCtrl(SCENE, storage, { onFinished });
        ctrl.start();
        ctrl.onSkip();
        expect(ctrl.status).toBe('finished');
        expect(onFinished).toHaveBeenCalledWith(true);
        expect(storage.get(STORAGE_KEY.StoryIntroSeen, false)).toBe(true);
    });

    it('re-skipping after finish is a no-op', () => {
        const ctrl = new StorySceneCtrl(SCENE, new StorageMgr(mem()));
        ctrl.start();
        ctrl.onSkip();
        expect(() => ctrl.onSkip()).not.toThrow();
    });
});

describe('StorySceneCtrl — reset() (req 19.6)', () => {
    it('clears the "seen" flag', () => {
        const storage = new StorageMgr(mem());
        const ctrl = new StorySceneCtrl(SCENE, storage);
        ctrl.start();
        ctrl.onSkip();
        expect(ctrl.hasBeenSeen()).toBe(true);
        ctrl.reset();
        expect(ctrl.hasBeenSeen()).toBe(false);
    });
});

describe('StorySceneCtrl — natural finish (all pages)', () => {
    it('calls onFinished(false) after advancing past the last page', () => {
        const onFinished = jest.fn();
        const ctrl = new StorySceneCtrl(SCENE, new StorageMgr(mem()), { onFinished });
        ctrl.start();
        // Full page 1.
        ctrl.onTap();
        ctrl.onTap(); // → page 2
        ctrl.onTap(); // reveal page 2
        ctrl.onTap(); // → page 3
        ctrl.onTap(); // reveal page 3
        ctrl.onTap(); // → finish
        expect(onFinished).toHaveBeenCalledWith(false);
        expect(ctrl.status).toBe('finished');
    });
});
