/**
 * Pure compose functions for F5 (Export).
 *
 * No filesystem, no I/O, no Date.now() — every output is a function of the input.
 * Caller layer (Task 2+) handles fs writes, atomic rename, zip packaging.
 */

export interface ExportNoteMedia {
  rel: string;
  mime: string;
  bytes: number;
}

export interface ExportNoteTag {
  name: string;
  source: 'ai' | 'user';
}

export interface ExportNote {
  id: string;
  createdAt: string;
  updatedAt: string;
  rawText: string;
  aiTitle: string | null;
  aiSummary: string | null;
  titleEditedByUser: boolean;
  summaryEditedByUser: boolean;
  aiProvider: string | null;
  aiGeneratedAt: string | null;
  userIntent: string | null;
  intentPromptedAt: string | null;
  // v0.3.0 Cut E — Cut B (status), Cut C (dueDate via m002), and dueDate user-edited flag
  // need to round-trip through F5 export and Cut E sync.
  status: 'active' | 'completed' | 'archived' | 'trashed';
  statusChangedAt: string | null;
  moveReason: string | null;
  dueDate: string | null;
  dueDateEditedByUser: boolean;
  tags: ExportNoteTag[];
  media: ExportNoteMedia[];
}

const FORBIDDEN_FS_CHARS_REGEX = /[/\\:*?"<>|]/g;
const WHITESPACE_RUN_REGEX = /\s+/g;
const SLUG_MAX_CODEPOINTS = 32;

export function slugifyTitle(title: string | null): string {
  if (title === null || title === undefined) return 'untitled';
  // Strip forbidden characters first.
  const stripped = title.replace(FORBIDDEN_FS_CHARS_REGEX, '');
  // Collapse whitespace runs to a single hyphen.
  const hyphenated = stripped.replace(WHITESPACE_RUN_REGEX, '-');
  // Trim leading/trailing hyphens.
  const trimmed = hyphenated.replace(/^-+|-+$/g, '');
  if (trimmed.length === 0) return 'untitled';
  // Truncate to 32 unicode codepoints (handle Korean / emoji properly).
  const cps = [...trimmed];
  const truncated = cps.length > SLUG_MAX_CODEPOINTS
    ? cps.slice(0, SLUG_MAX_CODEPOINTS).join('')
    : trimmed;
  // Re-trim hyphens in case truncation landed on one.
  const finalSlug = truncated.replace(/^-+|-+$/g, '');
  return finalSlug.length === 0 ? 'untitled' : finalSlug;
}

export function composeFilename(input: {
  id: string;
  createdAt: string;
  aiTitle: string | null;
}): string {
  const date = input.createdAt.slice(0, 10);
  const id8 = input.id.slice(0, 8);
  const slug = slugifyTitle(input.aiTitle);
  return `${date}-${id8}-${slug}.md`;
}

// ---------------------------------------------------------------------------
// YAML quoting helpers
// ---------------------------------------------------------------------------

const YAML_SPECIAL_CHARS = [':', "'", '"', '#', '[', ']', '{', '}'];
const YAML_LEADING_INDICATORS = ['-', '?', '!', '&', '*', '>'];

function needsQuoting(value: string): boolean {
  if (value.length === 0) return true;
  // Leading/trailing whitespace.
  if (value !== value.trim()) return true;
  // Embedded special chars that conflict with plain-scalar parsing.
  for (const ch of YAML_SPECIAL_CHARS) {
    if (value.includes(ch)) return true;
  }
  // Leading indicators.
  const first = value.charAt(0);
  if (YAML_LEADING_INDICATORS.includes(first)) return true;
  return false;
}

function singleQuote(value: string): string {
  return `'${value.replace(/'/g, "''")}'`;
}

/**
 * Format a string scalar for YAML.
 *  - Multiline → block scalar `|-` with given indent.
 *  - Otherwise → plain or single-quoted depending on contents.
 *
 * `indent` is the indent applied to body lines of the block scalar (default 2 spaces).
 */
function formatScalar(value: string, indent = 2): string {
  if (value.includes('\n')) {
    const pad = ' '.repeat(indent);
    const lines = value.split('\n').map((l) => `${pad}${l}`);
    return `|-\n${lines.join('\n')}`;
  }
  return needsQuoting(value) ? singleQuote(value) : value;
}

function formatTagName(name: string): string {
  // Names can't be multiline in flow style — fall back to single-quote when needed.
  if (name.includes('\n') || needsQuoting(name)) return singleQuote(name);
  return name;
}

// ---------------------------------------------------------------------------
// composeFrontmatter
// ---------------------------------------------------------------------------

export function composeFrontmatter(note: ExportNote): string {
  const lines: string[] = [];
  lines.push('---');
  lines.push(`id: ${note.id}`);
  lines.push(`created_at: ${note.createdAt}`);
  lines.push(`updated_at: ${note.updatedAt}`);

  if (note.aiTitle !== null) {
    lines.push(`title: ${formatScalar(note.aiTitle)}`);
    lines.push(`title_source: ${note.titleEditedByUser ? 'user' : 'ai'}`);
  }

  if (note.aiSummary !== null) {
    lines.push(`summary: ${formatScalar(note.aiSummary)}`);
    lines.push(`summary_source: ${note.summaryEditedByUser ? 'user' : 'ai'}`);
  }

  if (note.tags.length > 0) {
    lines.push('tags:');
    for (const tag of note.tags) {
      lines.push(`  - { name: ${formatTagName(tag.name)}, source: ${tag.source} }`);
    }
  }

  if (note.userIntent !== null) {
    lines.push(`user_intent: ${formatScalar(note.userIntent)}`);
  }
  if (note.intentPromptedAt !== null) {
    lines.push(`intent_prompted_at: ${note.intentPromptedAt}`);
  }
  if (note.aiProvider !== null) {
    lines.push(`ai_provider: ${formatScalar(note.aiProvider)}`);
  }
  if (note.aiGeneratedAt !== null) {
    lines.push(`ai_generated_at: ${note.aiGeneratedAt}`);
  }

  lines.push(`status: ${note.status}`);
  if (note.statusChangedAt !== null) {
    lines.push(`status_changed_at: ${note.statusChangedAt}`);
  }
  if (note.moveReason !== null) {
    lines.push(`move_reason: ${formatScalar(note.moveReason)}`);
  }
  if (note.dueDate !== null) {
    lines.push(`due_date: ${note.dueDate}`);
    lines.push(`due_date_source: ${note.dueDateEditedByUser ? 'user' : 'ai'}`);
  }

  if (note.media.length > 0) {
    lines.push('images:');
    for (const m of note.media) {
      lines.push(`  - rel: ${formatScalar(m.rel)}`);
      lines.push(`    mime: ${formatScalar(m.mime)}`);
      lines.push(`    bytes: ${m.bytes}`);
    }
  }

  lines.push('inkling_export_version: 1');
  lines.push('---');
  return lines.join('\n') + '\n';
}

// ---------------------------------------------------------------------------
// composeMarkdown
// ---------------------------------------------------------------------------

function extFromMime(mime: string): string {
  switch (mime) {
    case 'image/png':
      return 'png';
    case 'image/jpeg':
      return 'jpg';
    default:
      return 'bin';
  }
}

export function composeMarkdown(note: ExportNote): string {
  const fm = composeFrontmatter(note);
  const heading = `# ${note.aiTitle ?? '(제목 없음)'}`;
  const id8 = note.id.slice(0, 8);

  const sections: string[] = [];
  sections.push(fm.trimEnd()); // remove trailing newline so we control spacing
  sections.push(heading);
  if (note.aiSummary !== null) {
    sections.push(`> ${note.aiSummary}`);
  }
  sections.push(note.rawText);

  if (note.media.length > 0) {
    const imageLines = note.media.map((m, idx) => {
      const n = idx + 1;
      const ext = extFromMime(m.mime);
      return `![](media/${id8}__${n}.${ext})`;
    });
    sections.push(imageLines.join('\n'));
  }

  return sections.join('\n\n') + '\n';
}

// ---------------------------------------------------------------------------
// composeIndexJsonl
// ---------------------------------------------------------------------------

export function composeIndexJsonl(
  entries: Array<{ note: ExportNote; path: string }>
): string {
  if (entries.length === 0) return '';
  const lines = entries.map(({ note, path }) =>
    JSON.stringify({
      id: note.id,
      path,
      created_at: note.createdAt,
      tags: note.tags.map((t) => t.name),
      embedding_text: note.rawText
    })
  );
  return lines.join('\n') + '\n';
}

// ---------------------------------------------------------------------------
// composeManifest
// ---------------------------------------------------------------------------

export function composeManifest(input: {
  exportedAt: string;
  noteCount: number;
  mediaCount: number;
}): string {
  return JSON.stringify(
    {
      inkling_export_version: 1,
      exported_at: input.exportedAt,
      note_count: input.noteCount,
      media_count: input.mediaCount
    },
    null,
    2
  );
}
