// 그란데 독서타임 — 데이터 레이어
// 베타: localStorage 기반 직접 입력. 실제 운영 시 PRD §3 슬랙 워크플로 자동 수집으로 전환.
// - 시드: 2026-04-24 첫 회차 6권 (이름·책 제목만)
// - 저자/출판사/페이지/장르/인용문은 관리 탭에서 점진적 보강 (자동 보강 버튼 사용 가능)
// - readers: 한 권에 여러 명이 함께 공유 가능 (배열). 기존 단일 reader 데이터는 자동 마이그레이션
// - quoteBy: 인용문을 남긴 사람 (readers 중 한 명 또는 별도 명시)

// pagesRead 필드 추가 — 책 전체 두께(pages)는 spine 시각용,
//     pagesRead는 실제 읽은 분량 → 책탑 cm·구성원 통계 산정 기준
// 기존 v7 데이터는 load 마이그레이션에서 pagesRead = pages (완독 가정)으로 보정
const STORAGE_KEY = 'grandeReadingTime.books.v7';

// 공용 책 목록 백엔드 — 신청과 동일 Apps Script 웹앱 /exec. 시트 '책목록'에서 읽음.
// (Apps Script에 doGet(?action=books) 추가 + 재배포 후 활성. 그 전엔 시드로 폴백)
const BOOKS_ENDPOINT = 'https://script.google.com/macros/s/AKfycbwRygO5DaybmUOnVTGm78vO2MgV24xHnkG6w7y-j4MWOn9jimwCII_kQnwXD0AY07Fo/exec';

// 구성원 이름 → 소속 매핑 (paste 등으로 role 없이 추가된 책의 누락 보정용)
// 마스터 시트: Google Sheets 1P6VhGa8WGlPid0Ms2SCO75HOLxF6_uvbG7RH6SycfRw
// 새 구성원 추가는 여기에 한 줄씩 늘리면 됨
const memberRoles = {
  '김봉진': '그란데클립',
  '김선진': '매거진C팀',
};

// 공용 시트 '책목록'(Apps Script) 기준 24권. 배포 시드 = 즉시/오프라인 폴백.
// 최신·공유 목록은 fetchRemoteBooks()가 시트에서 비동기로 가져와 덮어씀.
const seedBooks = [
  {"id": 2, "title": "모순", "author": "양귀자", "publisher": "쓰다", "pages": 308, "pagesRead": 308, "genre": "소설", "readers": ["김아현"], "reader": "김아현", "role": "", "date": "2026.05.15", "quote": "", "quoteBy": "", "thumbnail": "https://image.aladin.co.kr/product/2584/37/cover500/8998441012_3.jpg"},
  {"id": 3, "title": "크리에이티브 점프", "author": "류자키 쇼코", "publisher": "북스톤", "pages": 304, "pagesRead": 304, "genre": "경제경영", "readers": ["조미라"], "reader": "조미라", "role": "", "date": "2026.05.15", "quote": "", "quoteBy": "", "thumbnail": "https://image.aladin.co.kr/product/35637/0/cover500/k072036503_3.jpg"},
  {"id": 5, "title": "내 아이를 위한 감정코칭", "author": "최성애, 조벽, 존 가트맨", "publisher": "해냄출판사", "pages": 356, "pagesRead": 356, "genre": "인문", "readers": ["이동규"], "reader": "이동규", "role": "", "date": "2026.05.15", "quote": "", "quoteBy": "", "thumbnail": "https://image.aladin.co.kr/product/23332/19/cover500/8965749867_1.jpg"},
  {"id": 11, "title": "그저 앉기를 권함", "author": "스즈키 슌류", "publisher": "쌤앤파커스", "pages": 264, "pagesRead": 264, "genre": "사회", "readers": ["이선아"], "reader": "이선아", "role": "", "date": "2026.05.15", "quote": "위대한 현자는 바로 여러분 자신입니다.", "quoteBy": "이선아", "thumbnail": "https://image.aladin.co.kr/product/37029/10/cover500/k952030839_1.jpg"},
  {"id": 13, "title": "네이키드 애자일(Naked Agile)", "author": "장재웅, 상효이재", "publisher": "미래의창", "pages": 312, "pagesRead": 40, "genre": "경제경영", "readers": ["김규리"], "reader": "김규리", "role": "", "date": "2026.05.15", "quote": "보편적 인간과 집단에 있어 변화는 늘 마치 ‘질병’처럼 두려운 존재이자, 저항의 대상이 되어왔다. (중략) 디지털 혁명, 그로 인한 시장의 파괴, 문화적 세대 교체 등 지금은 거대 전환의 시기라 할 정도로 우리 주변을 둘러싼 사회·경제·기술적 환경 변화의 규모와 속도가 갈수록 거대해지고 빨라지고 있다. 그리고 그 변화는 점점 더 예측하기 힘든 양상을 보인다. 소설 《페스트》에서 상상력을 좀 더 발휘해 ‘질병’을 ‘변화’라는 속성에 비추어보자. 우리는 파늘루 신부인가, 랑베르인가, 리유와 타루인가, 아니면 불특정 다수의 갈대 같은 군중인가?", "quoteBy": "김규리", "thumbnail": "https://image.aladin.co.kr/product/21773/12/cover500/8959896225_1.jpg"},
  {"id": 14, "title": "내가 틀릴 수도 있습니다", "author": "비욘 나티코 린데블라드", "publisher": "다산초당", "pages": 312, "pagesRead": 80, "genre": "사회", "readers": ["송의령"], "reader": "송의령", "role": "", "date": "2026.05.15", "quote": "'대기업 재무 담당자의 주요 동기는 무엇인가? 바로 주주의 이익을 극대화 하는 것이다.' 그 순간 저는 제가 주주의 이익에 아무런 관심이 없다는 사실을 깨달았습니다. '그게 나한테 무슨 의미가 있지? 내가 주주를 한 사람이라도 만난 적이 있나? 설사 만났다 해도, 내가 왜 그 사람들의 재산을 최대한으로 불리는데 전력을 기울여야하지?'", "quoteBy": "송의령", "thumbnail": "https://image.aladin.co.kr/product/29269/12/cover500/k822837807_3.jpg"},
  {"id": 15, "title": "데미안", "author": "헤르만 헤세", "publisher": "민음사", "pages": 248, "pagesRead": 248, "genre": "", "readers": ["민성규"], "reader": "민성규", "role": "", "date": "2026.05.15", "quote": "분명 나는 밝고 올바른 세계에 속했고 내 부모님의 자식이었다. 그러나 내 눈과 귀가 향하는 곳 어디에나 다른 세계가 있었다.\n\n나는 다른 세계 속에서도 살고 있었다. 비록 그런 것들이내게 낯설고 무서운 일이라 해도, 그래서 그곳에서 양심의 가책과 불안함이 있었다 하더라도, 내가 한동안 가장 살고 싶어 한 곳은 금지된 세계였다.\n\n그리고 밝은 세계로의 귀환은, (그것이 지극히 당연하고 옳은 것임에도) 더 아름답지 않은 것, 더 지루한 것, 더 무미건조한 세계로 돌아가는 것 같았다.", "quoteBy": "민성규", "thumbnail": "https://image.aladin.co.kr/product/26/0/cover500/s742633278_2.jpg"},
  {"id": 16, "title": "더블 클릭", "author": "알간지", "publisher": "생각정원", "pages": 312, "pagesRead": 312, "genre": "사회", "readers": ["김지호"], "reader": "김지호", "role": "", "date": "2026.05.15", "quote": "선택권은 늘 나에게 있다. 당장 사용하지 않으면 사라지는 것도 아니다. 내 ‘인생 피드’에 이상하고 부정적인 상황이 떠오르면 “나와”하고 원하는 방향을 향해 하나씩 다시 선택하면 된다. 다시 말하지만, 인생 피드에 이상한 영상이 떴다고 해서 인생 전체가 잘못되는건 아니다. 내가 클릭하지 않는다면, 그건 곧 사라진다. 내가 그 영상에 의미를 부여하지 않는다면, 그것은 더 이상 영향을 주지 못한다. 중요한 건, 상황에 대한 인식이다. 상황에 말리지 않고, 내가 원하는 방향으로 계속해서 다른 선택을 해 나가는 것이다. 그러다 보면 나를 둘러싼 상황이 바뀌면서 삶의 알고리즘도 달라진다.", "quoteBy": "김지호", "thumbnail": "https://image.aladin.co.kr/product/38361/32/cover500/k722135362_1.jpg"},
  {"id": 17, "title": "인피니트 게임", "author": "사이먼 사이넥", "publisher": "세계사", "pages": 328, "pagesRead": 94, "genre": "경제경영", "readers": ["김규리"], "reader": "김규리", "role": "", "date": "2026.05.22", "quote": "바빌로프는 대의명분을 추구하는 자신의 마음을 다음과 같이 표현했다. “우리는 장작더미로 걸어 들어가 타 죽을 수도 있다. 그러나 우리의 신념을 저버릴 수는 없다.", "quoteBy": "김규리", "thumbnail": "https://image.aladin.co.kr/product/29802/23/cover500/893387187x_1.jpg"},
  {"id": 18, "title": "일의 감각", "author": "조수용", "publisher": "REFERENCE BY B", "pages": 264, "pagesRead": 264, "genre": "경제경영", "readers": ["김소희"], "reader": "김소희", "role": "", "date": "2026.05.22", "quote": "존중과 신뢰야말로 자존감 있는 조직을 만들기 위한 기본입니다.", "quoteBy": "김소희", "thumbnail": "https://image.aladin.co.kr/product/35225/12/cover500/k752934734_2.jpg"},
  {"id": 19, "title": "사피엔스", "author": "유발 하라리", "publisher": "김영사", "pages": 648, "pagesRead": 207, "genre": "에세이", "readers": ["김상현"], "reader": "김상현", "role": "", "date": "2026.05.22", "quote": "쓰기는 인간의 의식을 돕는 하인으로 탄생했지만, 점점 더 우리의 주인이 되어가고 있다. (중략···) 세상의 새로운 통치자는 0과 1로 길게 늘어선 이진수들이 될 것이다.", "quoteBy": "김상현", "thumbnail": "https://image.aladin.co.kr/product/31424/4/cover500/k482832219_1.jpg"},
  {"id": 20, "title": "잘 살고 있는 건가 싶을 때", "author": "조성용", "publisher": "북갈피", "pages": 280, "pagesRead": 64, "genre": "자기계발", "readers": ["박나경"], "reader": "박나경", "role": "", "date": "2026.06.05", "quote": "아무런 의미도 눈빛도 없이 터덜터덜 살아가는 것이 아니라, 하루를 살더라도 또렷한 시선으로 살아내는 사람이 되고 싶다.", "quoteBy": "박나경", "thumbnail": "https://image.aladin.co.kr/product/35499/94/cover500/k832035653_1.jpg"},
  {"id": 21, "title": "비폭력대화", "author": "마셜 B. 로젠버그", "publisher": "한국NVC출판사", "pages": 380, "pagesRead": 380, "genre": "사회", "readers": ["김소희"], "reader": "김소희", "role": "", "date": "2026.06.05", "quote": "삶에서 내가 원하는 것은, 가슴에서 우러나와 서로 주고받을때 나와 다른사람 사이에서 흐르는 연민이다.", "quoteBy": "김소희", "thumbnail": "https://image.aladin.co.kr/product/34363/53/cover500/k442932009_2.jpg"},
  {"id": 22, "title": "일의 감각", "author": "조수용", "publisher": "REFERENCE BY B", "pages": 264, "pagesRead": 183, "genre": "경제경영", "readers": ["김규리"], "reader": "김규리", "role": "", "date": "2026.06.05", "quote": "어떤 일이 성공하려면 나만의 취향에서 한 발짝 더 나아가야 합니다. 나의 선호와 타인에 대한 공감이 만나는 지점, 서로 밀고 당기는 압력이 느껴지는 그 미세한 지점을 찾아내야 하는 것입니다. 내 취향과 세상의 취향이 만나는 지점입니다. 우선 '나'로부터 시작해야 합니다. 일단 무엇보다 내가 좋아하는 것에 대한 이해가 남달라야 합니다. 무언가를 좋아한다는 것은 그 분야를 잘 안다는 것을 전제로 하며, 많이 알면 알수록 더 구체적으로 좋아하게 됩니다.", "quoteBy": "김규리", "thumbnail": "https://image.aladin.co.kr/product/35225/12/cover500/k752934734_2.jpg"},
  {"id": 23, "title": "실패를 통과하는 일", "author": "박소령", "publisher": "북스톤", "pages": 336, "pagesRead": 336, "genre": "경제경영", "readers": ["전지연"], "reader": "전지연", "role": "", "date": "2026.06.05", "quote": "내가 좋아하는 달리기만큼이나 미친 짓도 흔치 않다. 달리기는 고통스럽고 위험한 운동이다. 보상이 적을 뿐만 아니라 그마저도 확실하게 받는다는 보장이 없다. 트랙이나 도로를 달릴 때 목적지가 정해져 있는 것도 아니다. 그 어떤 것도 이런 노력을 충분히 보상해주지 않는다. 오직 달리는 행위 자체가 목적일 뿐이다. 어느 누구도 결승선을 정해주지 않는다. 당신만이 결승선을 정할 수 있다. 혹시라도 당신이 달리는 행위로 기쁨을 얻더라도, 마음으로만 얻을 뿐이다. 당신은 달리기를 이런 시각에서 바라보면서, 당신 자신을 설득시킬 수 있어야 한다.", "quoteBy": "전지연", "thumbnail": "https://image.aladin.co.kr/product/37063/36/cover500/k672030333_3.jpg"},
  {"id": 24, "title": "데이터는 어떻게 인생의 무기가 되는가", "author": "세스 스티븐스 다비도위츠", "publisher": "더퀘스트", "pages": 364, "pagesRead": 364, "genre": "인문", "readers": ["이준호"], "reader": "이준호", "role": "", "date": "2026.06.05", "quote": "성공의 잘못된 통념2: 외톨이가 창업에 유리하다.\n창업자가 창업의 분야에 경험이 있으면 성공 확률이 2배\n성공의 잘못된 통념3: 주변부의 힘\n직원으로 성공했던 사람들이 창업에도 성공했다.", "quoteBy": "이준호", "thumbnail": "https://image.aladin.co.kr/product/30216/74/cover500/k372839494_1.jpg"},
  {"id": 25, "title": "Failed it!", "author": "Kessels Erik (CON)", "publisher": "Phaidon", "pages": 192, "pagesRead": 192, "genre": "IT/기술", "readers": ["조미라"], "reader": "조미라", "role": "", "date": "2026.06.05", "quote": "사물을 새로운 시각으로 바라보고, 다른 잠재력을 발견해보라. 적절한 방식, 보편적인 방식만 고수하는 일보다 창의력을 더 저해하는 건 없으니까.\n\n당신의 것이든, 남이 만든 것이든, 실수를 음미할 시간을 가져라. 처음에는 잘못으로만 보였던 것들이 다른 무언가를 위한 불씨가 될 수도 있다. 매혹적이고, 낯설고, 독창적인 어떤 것 말이다.", "quoteBy": "조미라", "thumbnail": "https://image.aladin.co.kr/product/6730/60/cover500/0714871192_2.jpg"},
  {"id": 26, "title": "일의 감각", "author": "조수용", "publisher": "REFERENCE BY B", "pages": 264, "pagesRead": 264, "genre": "경제경영", "readers": ["김규리"], "reader": "김규리", "role": "피플팀", "date": "2026.06.12", "quote": "브랜딩의 다른 말은 ‘소신을 찾아나서는 과정’입니다.", "quoteBy": "김규리", "thumbnail": "https://image.aladin.co.kr/product/35225/12/cover500/k752934734_2.jpg"},
  {"id": 27, "title": "인피니트 게임", "author": "사이먼 사이넥", "publisher": "세계사", "pages": 328, "pagesRead": 165, "genre": "경제경영", "readers": ["김규리"], "reader": "김규리", "role": "피플팀", "date": "2026.06.12", "quote": "자원에는 한정이 있지만 의지력은 무한하다. 그래서 의지력을 중요하게 여기는 기업이 자원에 편중된 기업보다 궁극적으로 회복 탄력성이 더 뛰어나다.", "quoteBy": "김규리", "thumbnail": "https://image.aladin.co.kr/product/29802/23/cover500/893387187x_1.jpg"},
  {"id": 28, "title": "자연이 우리를 행복하게 만들 수 있다면", "author": "미셸 르 방 키앵", "publisher": "프런트페이지", "pages": 264, "pagesRead": 264, "genre": "인문", "readers": ["우준영"], "reader": "우준영", "role": "", "date": "2026.06.12", "quote": "", "quoteBy": "", "thumbnail": "https://image.aladin.co.kr/product/32022/32/cover500/k572834780_1.jpg"},
  {"id": 29, "title": "다시, 기획은 2형식이다", "author": "남충식", "publisher": "휴먼큐브", "pages": 440, "pagesRead": 220, "genre": "경제경영", "readers": ["김아현"], "reader": "김아현", "role": "", "date": "2026.06.12", "quote": "단순히 주어진 문제를 푸는 사람이 아니라 능동적으로 문제를 찾아내는 사람", "quoteBy": "김아현", "thumbnail": "https://image.aladin.co.kr/product/37114/23/cover500/k342030458_1.jpg"},
  {"id": 30, "title": "사피엔스", "author": "유발 하라리", "publisher": "김영사", "pages": 648, "pagesRead": 648, "genre": "인문", "readers": ["김상현"], "reader": "김상현", "role": "", "date": "2026.06.12", "quote": "신뢰는 신용을 창조했고, 신용은 현실 경제를 성장시켰으며, 성장은 미래에 대한 신뢰를 강화하고 더 많은 신용을 향한 길을 열었다.", "quoteBy": "김상현", "thumbnail": "https://image.aladin.co.kr/product/31424/4/cover500/k482832219_1.jpg"},
  {"id": 31, "title": "우연의 의미를 찾아서", "author": "폴 핼퍼", "publisher": "위즈덤하우스", "pages": 460, "pagesRead": 460, "genre": "인문", "readers": ["김봉진"], "reader": "김봉진", "role": "그란데클립", "date": "2026.06.12", "quote": "", "quoteBy": "", "thumbnail": "https://image.aladin.co.kr/product/36890/27/cover500/k242030716_1.jpg"},
  {"id": 32, "title": "도덕경", "author": "노자", "publisher": "현대지성", "pages": 276, "pagesRead": 276, "genre": "인문", "readers": ["김예슬"], "reader": "김예슬", "role": "", "date": "2026.06.12", "quote": "", "quoteBy": "", "thumbnail": "https://image.aladin.co.kr/product/17665/17/cover500/k342534512_1.jpg"},
];

function cloneSeed() {
  return seedBooks.map(b => ({ ...b, readers: [...b.readers] }));
}

// readers 배열 추출 — 신규 readers 우선, 없으면 legacy reader 단일 → 배열
function readersOf(b) {
  if (Array.isArray(b.readers) && b.readers.length > 0) {
    return b.readers.map(r => (r || '').trim()).filter(Boolean);
  }
  if (b.reader) return [String(b.reader).trim()].filter(Boolean);
  return [];
}

// 한 권 정규화 — readers/reader 미러, pagesRead 폴백, quoteBy/role 자동 보정
function normalizeBook(b) {
  const readers = readersOf(b);
  const quote = b.quote || '';
  const quoteBy = b.quoteBy ? String(b.quoteBy).trim() : quote ? (readers[0] || '') : '';
  let role = b.role || '';
  if (!role) {
    for (const name of readers) {
      if (memberRoles[name]) { role = memberRoles[name]; break; }
    }
  }
  const pages = Number(b.pages) || 0;
  const pagesRead = (b.pagesRead != null && b.pagesRead !== '') ? (Number(b.pagesRead) || 0) : pages;
  return {
    id: b.id, title: b.title || '', author: b.author || '', publisher: b.publisher || '',
    pages, pagesRead, genre: b.genre || '', readers, reader: readers[0] || '', role,
    date: b.date || '', quote, quoteBy, thumbnail: b.thumbnail || '',
    done: b.done === true || b.done === 'true',
  };
}

// 초기 동기 로드 = 시드(배포본). localStorage는 더 이상 소스 아님 → 기기마다 달라지는 문제 제거.
// 최신·공유 목록은 fetchRemoteBooks()가 공용 시트에서 비동기로 가져와 덮어씀.
function loadBooks() {
  return cloneSeed().map(normalizeBook);
}

// 공용 시트(Apps Script /exec?action=books)에서 책 목록 JSONP로 가져오기.
// 실패/미배포/응답형식 불일치 시 null → 호출측이 시드 유지.
function fetchRemoteBooks() {
  return new Promise((resolve) => {
    if (!BOOKS_ENDPOINT || BOOKS_ENDPOINT.indexOf('script.google.com') === -1) { resolve(null); return; }
    const cb = 'rtBooks_' + Date.now() + '_' + Math.floor(Math.random() * 1e6);
    let done = false;
    const s = document.createElement('script');
    const cleanup = () => {
      try { delete window[cb]; } catch (_) { window[cb] = undefined; }
      if (s.parentNode) s.parentNode.removeChild(s);
      clearTimeout(timer);
    };
    const timer = setTimeout(() => { if (done) return; done = true; cleanup(); resolve(null); }, 12000);
    window[cb] = (data) => {
      if (done) return; done = true; cleanup();
      if (data && Array.isArray(data.books) && data.books.length) resolve(data.books.map(normalizeBook));
      else resolve(null);
    };
    s.src = BOOKS_ENDPOINT + '?action=books&callback=' + cb;
    s.onerror = () => { if (done) return; done = true; cleanup(); resolve(null); };
    document.body.appendChild(s);
  });
}

function saveBooks(books) {
  try {
    // readers 메인, reader는 mirror로 동기화
    // pagesRead 누락 시 pages로 자동 채움 (완독 가정)
    const normalized = books.map(b => {
      const readers = readersOf(b);
      const pages = Number(b.pages) || 0;
      const pagesRead = (b.pagesRead != null && b.pagesRead !== '')
        ? (Number(b.pagesRead) || 0)
        : pages;
      return { ...b, readers, reader: readers[0] || '', pages, pagesRead };
    });
    localStorage.setItem(STORAGE_KEY, JSON.stringify(normalized));
  } catch (e) {
    // localStorage 미지원/용량 초과 시 무시
  }
}

function nextId(books) {
  return books.reduce((m, b) => Math.max(m, Number(b.id) || 0), 0) + 1;
}

// 페이지수 → 책탑 cm: PRD §6.4 generated column 공식 (page × 0.08mm = page × 0.008cm)
function pagesToCm(pages) {
  return +((pages || 0) * 0.008).toFixed(1);
}

// 책탑·통계 산정 기준은 pagesRead(실제 읽은 페이지). pagesRead 누락 시 pages로 fallback
const readPagesOf = (b) => {
  if (b.pagesRead != null && b.pagesRead !== '') return Number(b.pagesRead) || 0;
  return Number(b.pages) || 0;
};

function computeDerived(books) {
  const memberMap = {};
  // 한 권에 readers 여러 명이면 각자 +1권, +pagesRead 가산
  books.forEach(b => {
    const rs = readersOf(b);
    const read = readPagesOf(b);
    rs.forEach(name => {
      if (!memberMap[name]) memberMap[name] = { name, role: b.role || '', books: 0, pages: 0, height: 0 };
      memberMap[name].books += 1;
      memberMap[name].pages += read;
      if (b.role && !memberMap[name].role) memberMap[name].role = b.role;
    });
  });
  Object.values(memberMap).forEach(m => { m.height = pagesToCm(m.pages); });
  const members = Object.values(memberMap).sort((a, b) =>
    b.books - a.books || b.pages - a.pages || a.name.localeCompare(b.name, 'ko')
  );

  const genreMap = {};
  books.forEach(b => {
    const g = (b.genre || '').trim() || '미분류';
    const read = readPagesOf(b);
    if (!genreMap[g]) genreMap[g] = { genre: g, count: 0, pages: 0 };
    genreMap[g].count += 1;
    genreMap[g].pages += read;
  });
  const genres = Object.values(genreMap).sort((a, b) => b.count - a.count);

  const totalBooks = books.length;
  const totalPages = books.reduce((s, b) => s + readPagesOf(b), 0);
  const totalHeightCm = pagesToCm(totalPages);
  const stats = { totalBooks, totalPages, totalHeightCm, members: members.length };

  return { members, stats, genres };
}

// quote가 있는 모든 책을 인용 객체 배열로 반환 (그란데 말입니다 회전용)
function allQuotes(books) {
  return books
    .filter(b => (b.quote || '').trim().length > 0)
    .map(b => {
      const rs = readersOf(b);
      return {
        text: b.quote,
        book: b.title || '',
        author: b.author || '',
        reader: (b.quoteBy && b.quoteBy.trim()) || rs[0] || '',
      };
    });
}

// 오늘의 한 문장 — 날짜 기반 deterministic 1개 선택
function pickTodayQuote(books) {
  const withQuote = books.filter(b => (b.quote || '').trim().length > 0);
  if (withQuote.length === 0) {
    return {
      text: '아직 등록된 인용문이 없어요. 관리에서 인상 깊은 문장을 남겨보세요.',
      book: '', author: '', reader: '',
    };
  }
  const d = new Date();
  const seed = d.getFullYear() * 10000 + (d.getMonth() + 1) * 100 + d.getDate();
  const pick = withQuote[seed % withQuote.length];
  const rs = readersOf(pick);
  return {
    text: pick.quote,
    book: pick.title || '',
    author: pick.author || '',
    reader: (pick.quoteBy && pick.quoteBy.trim()) || rs[0] || '',
  };
}

// 다음 목표 cm — 마일스톤 기반
// 30 → 50 → 100 (1m) → 이후 50cm 단위
function nextGoalCm(cm) {
  if (cm < 30) return 30;
  if (cm < 50) return 50;
  if (cm < 100) return 100;
  return Math.ceil((cm + 1) / 50) * 50;
}

window.bookStore = {
  STORAGE_KEY,
  seed: seedBooks,
  load: loadBooks,
  fetchRemote: fetchRemoteBooks,
  save: saveBooks,
  nextId,
  derive: computeDerived,
  pagesToCm,
  pickTodayQuote,
  allQuotes,
  nextGoalCm,
  readersOf,
  readPagesOf,
  resetToSeed: () => {
    const fresh = cloneSeed();
    saveBooks(fresh);
    return fresh;
  },
};
