import { mergePlaybackProgress } from "../../domain/progress.ts";
import type { PlaybackProgress } from "../../domain/types.ts";
import type { D1DatabaseLike } from "../d1.ts";

interface ProgressRow {
  book_id: string;
  chapter_id: string;
  segment_id: string;
  offset_ms: number;
  playback_rate: number;
  updated_at: string;
}

function publicProgress(row: ProgressRow): PlaybackProgress {
  return {
    bookId: row.book_id,
    chapterId: row.chapter_id,
    segmentId: row.segment_id,
    offsetMs: row.offset_ms,
    playbackRate: row.playback_rate,
    updatedAt: row.updated_at,
  };
}

export class ProgressRepository {
  constructor(privateDatabase: D1DatabaseLike) {
    this.database = privateDatabase;
  }

  private readonly database: D1DatabaseLike;

  async get(bookId: string): Promise<PlaybackProgress | null> {
    const row = await this.database.prepare("SELECT * FROM playback_progress WHERE book_id = ? LIMIT 1")
      .bind(bookId).first<ProgressRow>();
    return row ? publicProgress(row) : null;
  }

  async save(bookId: string, input: Omit<PlaybackProgress, "bookId">): Promise<PlaybackProgress> {
    const ownership = await this.database.prepare(`
      SELECT segment.id
      FROM segments segment
      JOIN chapters chapter ON chapter.id = segment.chapter_id
      WHERE segment.id = ? AND chapter.id = ? AND chapter.book_id = ?
      LIMIT 1
    `).bind(input.segmentId, input.chapterId, bookId).first<{ id: string }>();
    if (!ownership) throw new Error("章节或片段不属于这本书。");
    const merged = mergePlaybackProgress(await this.get(bookId), { bookId, ...input });
    await this.database.batch([
      this.database.prepare(`
        INSERT INTO playback_progress (
          book_id, chapter_id, segment_id, offset_ms, playback_rate, updated_at
        ) VALUES (?, ?, ?, ?, ?, ?)
        ON CONFLICT(book_id) DO UPDATE SET
          chapter_id = excluded.chapter_id,
          segment_id = excluded.segment_id,
          offset_ms = excluded.offset_ms,
          playback_rate = excluded.playback_rate,
          updated_at = excluded.updated_at
        WHERE excluded.updated_at >= playback_progress.updated_at
      `).bind(merged.bookId, merged.chapterId, merged.segmentId, merged.offsetMs,
        merged.playbackRate, merged.updatedAt),
      this.database.prepare(`
        UPDATE books SET
          last_listened_at = CASE WHEN last_listened_at IS NULL OR last_listened_at <= ? THEN ? ELSE last_listened_at END,
          updated_at = CASE WHEN updated_at <= ? THEN ? ELSE updated_at END
        WHERE id = ?
      `).bind(merged.updatedAt, merged.updatedAt, merged.updatedAt, merged.updatedAt, bookId),
    ]);
    return (await this.get(bookId))!;
  }
}
