// @vitest-environment jsdom
import { describe, it, expect, vi, beforeEach } from 'vitest';
import '@testing-library/jest-dom/vitest';
import { render, screen, fireEvent, cleanup, waitFor } from '@testing-library/react';
import type { Note, Notebook } from '@shared/types';

const { mockOpenMedia, mockSetStatus, mockClassify, mockUpdateRawText, mockMoveNoteToNotebook } = vi.hoisted(() => ({
  mockOpenMedia: vi.fn(async () => ({ ok: true })),
  mockSetStatus: vi.fn(async () => ({ ok: true as const })),
  mockClassify: vi.fn(async () => ({
    recommended: 'archived' as const,
    rationale: 'stub'
  })),
  mockUpdateRawText: vi.fn(async () => ({ ok: true as const })),
  mockMoveNoteToNotebook: vi.fn(async () => {})
}));

vi.mock('../../src/renderer/inbox/api.js', () => ({
  inboxApi: {
    openMedia: mockOpenMedia,
    deleteNote: vi.fn(),
    restoreNote: vi.fn(),
    permanentDeleteNote: vi.fn(),
    updateAiFields: vi.fn(),
    setDueDate: vi.fn(),
    setIntent: vi.fn(),
    dismissIntent: vi.fn(),
    setStatus: mockSetStatus,
    classifyStatus: mockClassify,
    updateRawText: mockUpdateRawText,
    listRevisions: vi.fn(async () => []),
    restoreRevision: vi.fn(async () => ({ ok: true as const }))
  }
}));

const mockRefreshMeta = vi.fn();

// Notebooks used across notebook-chip tests.
const stubNotebooks: Notebook[] = [
  { id: 'nb-1', name: '회사', color: '#4a90d9', createdAt: '2026-01-01T00:00:00Z', updatedAt: '2026-01-01T00:00:00Z', noteCount: 1 },
  { id: 'nb-2', name: '개인', color: '#e67e22', createdAt: '2026-01-01T00:00:00Z', updatedAt: '2026-01-01T00:00:00Z', noteCount: 0 }
];

vi.mock('../../src/renderer/inbox/store.js', () => ({
  useInbox: Object.assign(
    // Selector-aware: if selector is a function, call it with the mock state.
    (selector?: (s: unknown) => unknown) => {
      const state = {
        notebooks: stubNotebooks,
        moveNoteToNotebook: mockMoveNoteToNotebook
      };
      if (typeof selector === 'function') return selector(state);
      return state;
    },
    { getState: () => ({ setTagFilter: vi.fn(), refreshMeta: mockRefreshMeta }) }
  )
}));

import { NoteCard } from '../../src/renderer/inbox/components/NoteCard';

const baseNote: Note = {
  id: 'n1',
  rawText: 'test',
  aiTitle: 'T',
  aiSummary: 'S',
  aiStatus: 'done',
  aiError: null,
  aiProvider: null,
  aiGeneratedAt: '2026-05-09T00:00:00Z',
  titleEditedByUser: false,
  summaryEditedByUser: false,
  userIntent: null,
  intentPromptedAt: '2026-05-09T00:00:00Z',
  dueDate: null,
  dueDateEditedByUser: false,
  deletedAt: null,
  lastRecalledAt: null,
  recallDismissedAt: null,
  status: 'active',
  statusChangedAt: null,
  moveReason: null,
  createdAt: '2026-05-09T00:00:00Z',
  updatedAt: '2026-05-09T00:00:00Z',
  tags: [],
  media: [
    { id: 'm1', kind: 'image', relPath: 'media/n1/img1.png', mime: 'image/png', bytes: 100 },
    { id: 'm2', kind: 'image', relPath: 'media/n1/img2.jpg', mime: 'image/jpeg', bytes: 200 }
  ],
  notebookId: 'nb-default'
};

describe('NoteCard — image rendering', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    cleanup();
  });

  it('renders <img> for each media item', () => {
    render(<NoteCard note={baseNote} onUpdated={() => {}} mode="inbox" />);
    const imgs = screen.getAllByRole('presentation');
    expect(imgs).toHaveLength(2);
    expect(imgs[0]?.getAttribute('src')).toBe('inkling-media://media/n1/img1.png');
    expect(imgs[1]?.getAttribute('src')).toBe('inkling-media://media/n1/img2.jpg');
  });

  it('clicking <img> calls inboxApi.openMedia', () => {
    render(<NoteCard note={baseNote} onUpdated={() => {}} mode="inbox" />);
    const first = screen.getAllByRole('presentation')[0];
    if (first === undefined) throw new Error('expected at least one img');
    fireEvent.click(first);
    expect(mockOpenMedia).toHaveBeenCalledWith('media/n1/img1.png');
  });
});

describe('NoteCard — ai_status=disabled fallback (v0.2.9 Cut B Task 13)', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    cleanup();
  });

  it('ai_status=disabled: title fallback to raw_text first line, hide summary/tags', () => {
    const disabledNote: Note = {
      ...baseNote,
      aiStatus: 'disabled',
      aiTitle: null,
      aiSummary: 'should-not-show',
      tags: [{ name: 't1', source: 'user' }],
      rawText: '첫 줄 본문\n둘째 줄 본문'
    };
    render(<NoteCard note={disabledNote} mode="inbox" onUpdated={vi.fn()} />);
    expect(screen.getByText('첫 줄 본문')).toBeInTheDocument();
    expect(screen.queryByText('should-not-show')).toBeNull();
    expect(screen.queryByText('t1')).toBeNull();
  });

  it('ai_status=disabled: empty raw → "(빈 메모)" fallback', () => {
    const disabledNote: Note = {
      ...baseNote,
      aiStatus: 'disabled',
      aiTitle: null,
      rawText: ''
    };
    render(<NoteCard note={disabledNote} mode="inbox" onUpdated={vi.fn()} />);
    expect(screen.getByText('(빈 메모)')).toBeInTheDocument();
  });
});

describe('NoteCard — 이동 버튼', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    cleanup();
  });

  it('이동 클릭 → MoveStatusModal 열림', () => {
    render(<NoteCard note={baseNote} onUpdated={() => {}} mode="inbox" />);
    fireEvent.click(screen.getByRole('button', { name: '이동' }));
    expect(screen.getByRole('dialog', { name: '이동' })).toBeInTheDocument();
  });

  it('Modal 내부 "완료" 버튼 → setStatus 호출 + onUpdated + onDeleted + refreshMeta', async () => {
    const onUpdated = vi.fn();
    const onDeleted = vi.fn();
    render(
      <NoteCard
        note={baseNote}
        onUpdated={onUpdated}
        onDeleted={onDeleted}
        mode="inbox"
      />
    );
    fireEvent.click(screen.getByRole('button', { name: '이동' }));
    fireEvent.click(screen.getByRole('button', { name: '완료' }));
    await waitFor(() => {
      expect(mockSetStatus).toHaveBeenCalledWith('n1', 'completed', null);
      expect(onUpdated).toHaveBeenCalled();
      expect(onDeleted).toHaveBeenCalled();
      expect(mockRefreshMeta).toHaveBeenCalled();
    });
  });
});

describe('NoteCard — raw_text editing', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    cleanup();
  });

  it('원문 편집: textarea 저장 → updateRawText 호출 + 로컬 raw 갱신', async () => {
    const onUpdated = vi.fn();
    render(<NoteCard note={{ ...baseNote, rawText: 'old' }} onUpdated={onUpdated} mode="inbox" />);
    // 원문 펼침
    fireEvent.click(screen.getByRole('button', { name: /원문/ }));
    // 편집 진입
    fireEvent.click(screen.getByRole('button', { name: '편집' }));
    const ta = screen.getByRole('textbox', { name: /원문 편집/ }) as HTMLTextAreaElement;
    fireEvent.change(ta, { target: { value: 'new' } });
    fireEvent.click(screen.getByRole('button', { name: '저장' }));

    await waitFor(() => {
      expect(mockUpdateRawText).toHaveBeenCalledWith('n1', 'new');
    });
    await waitFor(() => {
      expect(onUpdated).toHaveBeenCalled();
    });
    const last = onUpdated.mock.calls.at(-1)![0] as { rawText: string };
    expect(last.rawText).toBe('new');
  });
});

describe('NoteCard — notebook chip (Task 17)', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    cleanup();
  });

  it('현재 notebook 이름 chip 렌더링', () => {
    render(<NoteCard note={{ ...baseNote, notebookId: 'nb-1' }} onUpdated={vi.fn()} mode="inbox" />);
    expect(screen.getByTitle('다른 노트북으로 이동')).toBeInTheDocument();
    expect(screen.getByTitle('다른 노트북으로 이동').textContent).toContain('회사');
  });

  it('chip 클릭 → 다른 notebook 목록 dropdown', () => {
    render(<NoteCard note={{ ...baseNote, notebookId: 'nb-1' }} onUpdated={vi.fn()} mode="inbox" />);
    fireEvent.click(screen.getByTitle('다른 노트북으로 이동'));
    // 현재 nb-1('회사') 는 제외, nb-2('개인') 만 보임.
    expect(screen.getByText('개인')).toBeInTheDocument();
    expect(screen.queryAllByText('회사').length).toBe(1); // chip 버튼 안에만 존재
  });

  it('dropdown 의 notebook 클릭 → store.moveNoteToNotebook 호출', async () => {
    render(<NoteCard note={{ ...baseNote, notebookId: 'nb-1' }} onUpdated={vi.fn()} mode="inbox" />);
    fireEvent.click(screen.getByTitle('다른 노트북으로 이동'));
    fireEvent.click(screen.getByText('개인'));
    await waitFor(() => {
      expect(mockMoveNoteToNotebook).toHaveBeenCalledWith('n1', 'nb-2');
    });
  });
});
