import { mkdir, writeFile, copyFile, stat } from 'node:fs/promises';
import { join } from 'node:path';
import type { NoteRepository } from '../repository/NoteRepository.js';
import type { MediaStore } from './MediaStore.js';
import type { Note } from '@shared/types';
import {
  composeFilename,
  composeMarkdown,
  composeIndexJsonl,
  composeManifest,
  type ExportNote
} from './exportFormat.js';

export interface ExportOptions {
  includeMedia: boolean;
}

export interface ExportResult {
  outDir: string;
  noteCount: number;
  mediaCount: number;
  bytes: number;
}

const README_BODY = `# Inkling Export

이 폴더는 Inkling 메모 앱이 내보낸 마크다운 + 메타데이터 트리입니다.

## 구조

- \`notes/\` — 노트 1건당 마크다운 1파일. YAML frontmatter 에 id, 날짜, 태그, AI 메타 포함.
- \`media/\` — 노트에 첨부된 이미지. \`{id8}__{n}.{ext}\` 명명.
- \`index.jsonl\` — 노트별 RAG 친화 1줄 메타. \`embedding_text\` 는 raw_text 그대로.
- \`manifest.json\` — export 시각, 노트/미디어 수, 형식 버전.

## RAG 활용 예

LangChain / LlamaIndex 의 markdown loader 가 frontmatter 를 자동 파싱합니다. \`index.jsonl\` 을 직접 임베딩 입력으로 쓰면 별도 가공 없이 진행 가능.

## 형식 버전

\`inkling_export_version: 1\` — 향후 버전이 바뀌면 manifest.json 의 같은 필드로 식별.
`;

function inferExt(mime: string): string {
  if (mime === 'image/png') return 'png';
  if (mime === 'image/jpeg') return 'jpg';
  if (mime === 'image/gif') return 'gif';
  if (mime === 'image/webp') return 'webp';
  return 'bin';
}

function noteToExportNote(n: Note): ExportNote {
  return {
    id: n.id,
    createdAt: n.createdAt,
    updatedAt: n.updatedAt,
    rawText: n.rawText,
    aiTitle: n.aiTitle,
    aiSummary: n.aiSummary,
    titleEditedByUser: n.titleEditedByUser,
    summaryEditedByUser: n.summaryEditedByUser,
    aiProvider: n.aiProvider,
    aiGeneratedAt: n.aiGeneratedAt,
    userIntent: n.userIntent,
    intentPromptedAt: n.intentPromptedAt,
    status: n.status,
    statusChangedAt: n.statusChangedAt,
    moveReason: n.moveReason,
    dueDate: n.dueDate,
    dueDateEditedByUser: n.dueDateEditedByUser,
    tags: n.tags.map((t) => ({ name: t.name, source: t.source })),
    media: n.media.map((m, idx) => ({
      rel: `media/${n.id.slice(0, 8)}__${idx + 1}.${inferExt(m.mime)}`,
      mime: m.mime,
      bytes: m.bytes
    }))
  };
}

export class ExportService {
  constructor(
    private repo: NoteRepository,
    private mediaStore: MediaStore,
    private now: () => Date = () => new Date()
  ) {}

  async export(targetDir: string, opts: ExportOptions): Promise<ExportResult> {
    await mkdir(join(targetDir, 'notes'), { recursive: true });
    if (opts.includeMedia) {
      await mkdir(join(targetDir, 'media'), { recursive: true });
    }

    const notes = this.repo.listAll();
    const indexEntries: Array<{ note: ExportNote; path: string }> = [];
    let totalBytes = 0;
    let mediaCount = 0;

    for (const n of notes) {
      const en = noteToExportNote(n);
      const filename = composeFilename({
        id: n.id,
        createdAt: n.createdAt,
        aiTitle: n.aiTitle
      });
      const noteRelPath = `notes/${filename}`;
      const noteAbsPath = join(targetDir, noteRelPath);
      const md = composeMarkdown(en);
      await writeFile(noteAbsPath, md, 'utf8');
      const st = await stat(noteAbsPath);
      totalBytes += st.size;
      indexEntries.push({ note: en, path: noteRelPath });

      if (opts.includeMedia) {
        for (let i = 0; i < n.media.length; i++) {
          const m = n.media[i]!;
          const ext = inferExt(m.mime);
          const flatName = `${n.id.slice(0, 8)}__${i + 1}.${ext}`;
          const src = this.mediaStore.absolutePath(m.relPath);
          const dst = join(targetDir, 'media', flatName);
          try {
            await copyFile(src, dst);
            const ms = await stat(dst);
            totalBytes += ms.size;
            mediaCount += 1;
          } catch {
            // skip missing media file but log via throw upstream? For now, swallow.
            // (production: caller's logger captures via try/catch around the export call)
          }
        }
      }
    }

    const indexJsonl = composeIndexJsonl(indexEntries);
    await writeFile(join(targetDir, 'index.jsonl'), indexJsonl, 'utf8');
    totalBytes += Buffer.byteLength(indexJsonl, 'utf8');

    const manifest = composeManifest({
      exportedAt: this.now().toISOString(),
      noteCount: notes.length,
      mediaCount
    });
    await writeFile(join(targetDir, 'manifest.json'), manifest, 'utf8');
    totalBytes += Buffer.byteLength(manifest, 'utf8');

    await writeFile(join(targetDir, 'README.md'), README_BODY, 'utf8');
    totalBytes += Buffer.byteLength(README_BODY, 'utf8');

    return {
      outDir: targetDir,
      noteCount: notes.length,
      mediaCount,
      bytes: totalBytes
    };
  }
}
