import { describe, it, expect } from 'vitest';
import {
  slugifyTitle,
  composeFilename,
  composeFrontmatter,
  composeMarkdown,
  composeIndexJsonl,
  composeManifest,
  type ExportNote
} from '@main/services/exportFormat.js';

const baseNote: ExportNote = {
  id: '014a3b9c-1234-7890-abcd-000000000001',
  createdAt: '2026-04-25T14:23:11.000Z',
  updatedAt: '2026-04-25T14:24:02.000Z',
  rawText: '회고 메모 본문',
  aiTitle: '주간 회고 PR 리뷰',
  aiSummary: '회고 양식 통일을 위한 메모.',
  titleEditedByUser: false,
  summaryEditedByUser: false,
  aiProvider: 'local-ollama/gemma4:e4b',
  aiGeneratedAt: '2026-04-25T14:23:34.000Z',
  userIntent: null,
  intentPromptedAt: null,
  status: 'active',
  statusChangedAt: null,
  moveReason: null,
  dueDate: null,
  dueDateEditedByUser: false,
  tags: [{ name: 'pr', source: 'ai' }, { name: 'review', source: 'user' }],
  media: []
};

describe('slugifyTitle', () => {
  it('converts spaces to single hyphen, preserves Korean', () => {
    expect(slugifyTitle('주간 회고 PR 리뷰')).toBe('주간-회고-PR-리뷰');
  });
  it('returns "untitled" for null', () => {
    expect(slugifyTitle(null)).toBe('untitled');
  });
  it('returns "untitled" for empty string', () => {
    expect(slugifyTitle('')).toBe('untitled');
  });
  it('returns "untitled" for whitespace-only', () => {
    expect(slugifyTitle('   ')).toBe('untitled');
  });
  it('strips filesystem-forbidden chars', () => {
    expect(slugifyTitle('foo/bar:baz*qux"<>|?\\')).toBe('foobarbazqux');
  });
  it('collapses multiple whitespace to single hyphen', () => {
    expect(slugifyTitle('a  b   c')).toBe('a-b-c');
  });
  it('trims leading/trailing hyphens', () => {
    expect(slugifyTitle('  hello  ')).toBe('hello');
  });
  it('truncates to 32 codepoints (Korean counted properly)', () => {
    const long = '가'.repeat(50);
    expect([...slugifyTitle(long)].length).toBeLessThanOrEqual(32);
  });
});

describe('composeFilename', () => {
  it('combines date prefix + id8 + slug + .md', () => {
    expect(composeFilename({
      id: '014a3b9c-1234-7890-abcd-000000000001',
      createdAt: '2026-04-25T14:23:11.000Z',
      aiTitle: '주간 회고'
    })).toBe('2026-04-25-014a3b9c-주간-회고.md');
  });
  it('uses untitled slug for null title', () => {
    expect(composeFilename({
      id: '01234567-aaaa-bbbb-cccc-000000000000',
      createdAt: '2026-04-25T14:23:11.000Z',
      aiTitle: null
    })).toBe('2026-04-25-01234567-untitled.md');
  });
});

describe('composeFrontmatter', () => {
  it('produces frontmatter with delimiters and inkling_export_version=1', () => {
    const fm = composeFrontmatter(baseNote);
    expect(fm.startsWith('---\n')).toBe(true);
    expect(fm.trimEnd().endsWith('---')).toBe(true);
    expect(fm).toContain('inkling_export_version: 1');
  });
  it('includes title and source=ai for non-edited title', () => {
    const fm = composeFrontmatter(baseNote);
    expect(fm).toContain('title: 주간 회고 PR 리뷰');
    expect(fm).toContain('title_source: ai');
  });
  it('marks source=user when edited', () => {
    const fm = composeFrontmatter({ ...baseNote, titleEditedByUser: true });
    expect(fm).toContain('title_source: user');
  });
  it('omits null fields', () => {
    const fm = composeFrontmatter(baseNote);
    expect(fm).not.toContain('user_intent:');
    expect(fm).not.toContain('intent_prompted_at:');
  });
  it('uses block scalar |- for multiline summary', () => {
    const fm = composeFrontmatter({ ...baseNote, aiSummary: 'line1\nline2' });
    expect(fm).toContain('summary: |-');
    expect(fm).toContain('  line1');
    expect(fm).toContain('  line2');
  });
  it('single-quotes title containing colon', () => {
    const fm = composeFrontmatter({ ...baseNote, aiTitle: 'a: b' });
    expect(fm).toContain("title: 'a: b'");
  });
  it('emits tags array with inline flow style', () => {
    const fm = composeFrontmatter(baseNote);
    expect(fm).toContain('tags:');
    expect(fm).toContain('- { name: pr, source: ai }');
    expect(fm).toContain('- { name: review, source: user }');
  });
  it('omits tags section when empty', () => {
    const fm = composeFrontmatter({ ...baseNote, tags: [] });
    expect(fm).not.toContain('tags:');
  });
  it('emits images array when media present', () => {
    const fm = composeFrontmatter({
      ...baseNote,
      media: [{ rel: 'media/014a3b9c__1.png', mime: 'image/png', bytes: 1234 }]
    });
    expect(fm).toContain('images:');
    expect(fm).toContain('rel: media/014a3b9c__1.png');
    expect(fm).toContain('mime: image/png');
    expect(fm).toContain('bytes: 1234');
  });

  it('always emits status: active for a default note', () => {
    const fm = composeFrontmatter(baseNote);
    expect(fm).toContain('status: active');
  });

  it('emits due_date and due_date_source together when dueDate present', () => {
    const fm = composeFrontmatter({ ...baseNote, dueDate: '2026-06-01', dueDateEditedByUser: true });
    expect(fm).toContain('due_date: 2026-06-01');
    expect(fm).toContain('due_date_source: user');
  });

  it('emits due_date_source: ai when dueDateEditedByUser is false', () => {
    const fm = composeFrontmatter({ ...baseNote, dueDate: '2026-06-01', dueDateEditedByUser: false });
    expect(fm).toContain('due_date: 2026-06-01');
    expect(fm).toContain('due_date_source: ai');
  });

  it('omits due_date and due_date_source when dueDate is null', () => {
    const fm = composeFrontmatter(baseNote);
    expect(fm).not.toContain('due_date:');
    expect(fm).not.toContain('due_date_source:');
  });

  it('emits move_reason when present', () => {
    const fm = composeFrontmatter({ ...baseNote, status: 'archived', moveReason: 'done for now' });
    expect(fm).toContain('status: archived');
    expect(fm).toContain('move_reason: done for now');
  });

  it('emits status_changed_at when present', () => {
    const fm = composeFrontmatter({ ...baseNote, statusChangedAt: '2026-05-01T00:00:00Z' });
    expect(fm).toContain('status_changed_at: 2026-05-01T00:00:00Z');
  });

  it('status/due_date/move_reason fields appear before images: in frontmatter', () => {
    const fm = composeFrontmatter({
      ...baseNote,
      dueDate: '2026-06-01',
      dueDateEditedByUser: false,
      media: [{ rel: 'media/014a3b9c__1.png', mime: 'image/png', bytes: 1 }]
    });
    const statusPos = fm.indexOf('status:');
    const imagesPos = fm.indexOf('images:');
    expect(statusPos).toBeGreaterThan(-1);
    expect(imagesPos).toBeGreaterThan(-1);
    expect(statusPos).toBeLessThan(imagesPos);
  });
});

describe('composeMarkdown', () => {
  it('includes h1 with title, blockquote summary, body', () => {
    const md = composeMarkdown(baseNote);
    expect(md).toContain('# 주간 회고 PR 리뷰');
    expect(md).toContain('> 회고 양식 통일을 위한 메모.');
    expect(md).toContain('회고 메모 본문');
  });
  it('uses fallback heading when title null', () => {
    const md = composeMarkdown({ ...baseNote, aiTitle: null });
    expect(md).toContain('# (제목 없음)');
  });
  it('omits blockquote when summary null', () => {
    const md = composeMarkdown({ ...baseNote, aiSummary: null });
    expect(md).not.toContain('>');
  });
  it('appends image refs with id8__n.ext naming', () => {
    const md = composeMarkdown({
      ...baseNote,
      media: [
        { rel: 'media/old/1.png', mime: 'image/png', bytes: 100 },
        { rel: 'media/old/2.jpg', mime: 'image/jpeg', bytes: 200 }
      ]
    });
    expect(md).toContain('![](media/014a3b9c__1.png)');
    expect(md).toContain('![](media/014a3b9c__2.jpg)');
  });
});

describe('composeIndexJsonl', () => {
  it('emits one JSON line per entry with trailing newline', () => {
    const out = composeIndexJsonl([
      { note: baseNote, path: 'notes/2026-04-25-014a3b9c-주간-회고.md' }
    ]);
    expect(out.endsWith('\n')).toBe(true);
    const lines = out.trimEnd().split('\n');
    expect(lines.length).toBe(1);
    const obj = JSON.parse(lines[0]!);
    expect(obj.id).toBe(baseNote.id);
    expect(obj.path).toBe('notes/2026-04-25-014a3b9c-주간-회고.md');
    expect(obj.tags).toEqual(['pr', 'review']);
    expect(obj.embedding_text).toBe('회고 메모 본문');
  });

  it('emits two lines for two entries', () => {
    const out = composeIndexJsonl([
      { note: baseNote, path: 'notes/a.md' },
      { note: { ...baseNote, id: '02xxxxxx-...', rawText: 'b' }, path: 'notes/b.md' }
    ]);
    expect(out.trimEnd().split('\n').length).toBe(2);
  });
});

describe('composeManifest', () => {
  it('emits pretty JSON with required fields', () => {
    const m = composeManifest({
      exportedAt: '2026-04-26T00:00:00.000Z',
      noteCount: 42,
      mediaCount: 17
    });
    const obj = JSON.parse(m);
    expect(obj.inkling_export_version).toBe(1);
    expect(obj.exported_at).toBe('2026-04-26T00:00:00.000Z');
    expect(obj.note_count).toBe(42);
    expect(obj.media_count).toBe(17);
  });
});
