import { describe, it, expect, beforeEach, vi } from 'vitest';
import { render, screen, fireEvent, cleanup } from '@testing-library/react';
import { Journal } from './Journal';
import { appStore } from '../../store';

// Phaser does not initialize under happy-dom. The Journal does not import
// Phaser directly, but the @testing-library cleanup chain can transitively
// pull modules that do — and we want to keep these tests isolated from
// the Phaser runtime regardless.
vi.mock('../../game/event-bus', () => ({
  eventBus: {
    on: vi.fn(),
    off: vi.fn(),
    emit: vi.fn(),
    removeAllListeners: vi.fn(),
  },
}));

function resetStore(harvested: string[] = []): void {
  appStore.setState({ harvestedFragmentIds: harvested });
}

describe('Journal (D-24 full-screen modal, MEMR-04, MEMR-05)', () => {
  beforeEach(() => {
    cleanup();
    resetStore();
  });

  it('returns null when open=false', () => {
    const { container } = render(<Journal open={false} onClose={() => {}} />);
    expect(container.firstChild).toBeNull();
  });

  it('renders the empty-state copy from uiStrings when no fragments are harvested', () => {
    render(<Journal open={true} onClose={() => {}} />);
    // From content/seasons/01-soil/ui-strings.yaml: "Nothing yet. Plant something."
    expect(screen.getByText('Nothing yet. Plant something.')).toBeTruthy();
  });

  it('renders the body of a harvested fragment in selectable DOM (MEMR-05)', () => {
    resetStore(['season1.soil.first-bloom']);
    render(<Journal open={true} onClose={() => {}} />);
    // The body in fragments.yaml starts with "The first thing that grew was rosemary."
    expect(screen.getByText(/The first thing that grew was rosemary/)).toBeTruthy();
    // And the article wrapper carries userSelect: text (MEMR-05).
    const article = document.querySelector('article[data-fragment-id="season1.soil.first-bloom"]');
    expect(article).not.toBeNull();
    expect((article as HTMLElement).style.userSelect).toBe('text');
  });

  it('groups fragments by Season (D-24)', () => {
    resetStore(['season1.soil.first-bloom', 'season1.soil.the-cat']);
    render(<Journal open={true} onClose={() => {}} />);
    // Section header text "Season 1"
    expect(screen.getByText('Season 1')).toBeTruthy();
    // Both fragments rendered.
    expect(screen.getByText(/The first thing that grew was rosemary/)).toBeTruthy();
    expect(screen.getByText(/The cat is missing now too/)).toBeTruthy();
  });

  it('the close button invokes the onClose callback exactly once', () => {
    const onClose = vi.fn();
    render(<Journal open={true} onClose={onClose} />);
    fireEvent.click(screen.getByRole('button', { name: /close journal/i }));
    expect(onClose).toHaveBeenCalledTimes(1);
  });

  it('aria-label on the dialog is "Memory Journal" (accessibility)', () => {
    render(<Journal open={true} onClose={() => {}} />);
    expect(screen.getByLabelText('Memory Journal')).toBeTruthy();
  });

  it('silently skips harvested ids that do not resolve to a known Fragment (defensive)', () => {
    resetStore(['season1.soil.first-bloom', 'season9.bogus.never-shipped']);
    render(<Journal open={true} onClose={() => {}} />);
    // Real one renders; bogus one does not crash, just doesn't appear.
    expect(screen.getByText(/The first thing that grew was rosemary/)).toBeTruthy();
    expect(screen.queryByText(/season9.bogus/)).toBeNull();
  });
});
