package store

import "database/sql"

// ruby.go: the ruby/furigana readings captured on ingest (шаг 3a, v4 schema).
// This is CAPTURE-ONLY storage — the reading layer that epub-v1 text extraction
// would otherwise silently drop (04-unhappy §4 / D9). Memory v2 (шаг 4) reads it
// to seed a glossary name-lock; nothing here injects into a prompt, so no field
// is snapshot- or wire-load-bearing (it never touches request_hash).
//
// The store is dumb storage (mirrors chunkstatus.go): the aggregation "one row per
// (base,reading) with first_chapter=MIN and occurrences=full-book count" happens in
// the pipeline (ingest.go); this layer only upserts and reads back.

// RubyReading is one (book, base, reading) row.
type RubyReading struct {
	BookID       string
	Base         string // the ruby body (kanji/base surface form)
	Reading      string // the <rt> reading (furigana)
	FirstChapter int    // MIN 1-based chapter the pair appears in
	Occurrences  int    // full-book count of the pair
}

// ReplaceRubyReadings replaces a book's ENTIRE ruby set with rows in one
// transaction (DELETE-by-book, then INSERT each). The caller (persistRuby)
// recomputes the whole-book aggregate on every ingest, so a full replace is the
// correct idempotency model: re-ingesting the identical source rewrites identical
// rows; a source edit converges every column to the truth — including a pair the
// edit REMOVED, which a prior upsert-only path left as a permanent phantom (a
// name-lock on a name no longer in the book — external-review #6). Scoped to
// book_id, so other books are untouched. Each row's BookID must equal bookID.
func (s *Store) ReplaceRubyReadings(bookID string, rows []RubyReading) error {
	ctx, cancel := opContext()
	defer cancel()
	tx, err := s.w.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer tx.Rollback()
	if _, err := tx.ExecContext(ctx, `DELETE FROM ruby_readings WHERE book_id = ?`, bookID); err != nil {
		return err
	}
	for _, rr := range rows {
		if _, err := tx.ExecContext(ctx, `
			INSERT INTO ruby_readings (book_id, base, reading, first_chapter, occurrences)
			VALUES (?, ?, ?, ?, ?)`,
			rr.BookID, rr.Base, rr.Reading, rr.FirstChapter, rr.Occurrences); err != nil {
			return err
		}
	}
	return tx.Commit()
}

// RubyReadingsForBook returns every captured reading for a book, ordered
// deterministically (first_chapter, base, reading) for a stable report/consumer.
func (s *Store) RubyReadingsForBook(bookID string) ([]RubyReading, error) {
	return queryAll(s.r, `
		SELECT base, reading, first_chapter, occurrences
		FROM ruby_readings WHERE book_id = ?
		ORDER BY first_chapter, base, reading`,
		func(rows *sql.Rows) (RubyReading, error) {
			rr := RubyReading{BookID: bookID}
			err := rows.Scan(&rr.Base, &rr.Reading, &rr.FirstChapter, &rr.Occurrences)
			return rr, err
		}, bookID)
}
