/* ============================================================
   data.jsx — seed content + tiny external store + actions.
   Exposes on window: PlannerStore, useStore, PlannerActions,
   computeDay, SEED, time helpers, MOOD_QUESTIONS.
   ============================================================ */
const React = window.React;

/* ---- cover colorways (the one place real color is allowed) ---- */
const CV = {
  oxblood:  { from: "#5b1c14", to: "#8a2a18", fg: "#f3e7d8" },
  forest:   { from: "#16352a", to: "#274d3a", fg: "#eef0e4" },
  cobalt:   { from: "#16243f", to: "#243b63", fg: "#e6ecf6" },
  mustard:  { from: "#7a5a12", to: "#b5891f", fg: "#1a1407" },
  plum:     { from: "#2c1733", to: "#4b2a55", fg: "#efe3f0" },
  rust:     { from: "#6b2c12", to: "#a8491f", fg: "#f6e8dc" },
  slate:    { from: "#23262b", to: "#3a3f47", fg: "#e9eaec" },
  teal:     { from: "#0c3034", to: "#175055", fg: "#e0eeee" },
  clay:     { from: "#5a3320", to: "#8a5635", fg: "#f4e7da" },
  ink:      { from: "#16140f", to: "#2c2820", fg: "#ede8dd" },
  signal:   { from: "#a8210e", to: "#d7321b", fg: "#fbe6df" },
  olive:    { from: "#3a3a18", to: "#5c5c28", fg: "#f0efdc" },
};

/* week calendar: Mon..Sun, today = Thu (idx 3) = 11 June 2026 */
const WEEK_DATES = ["08.06", "09.06", "10.06", "11.06", "12.06", "13.06", "14.06"];
const DAY_NAMES = ["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"];

/* ---- materials (timeLog entries: { d: dayIdx, min }) ---------- */
const MATERIALS = [
  { id: "m1", type: "fiction", title: "Бесы", author: "Ф. Достоевский", cover: CV.oxblood,
    status: "reading", rating: 4, tags: ["роман", "XIX век", "политика"],
    progress: { current: 248, total: 768, unit: "стр." },
    timeLog: [{ d: 0, min: 40 }, { d: 1, min: 35 }, { d: 2, min: 50 }, { d: 3, min: 30 }],
    quotes: [
      { id: "q1", page: 211, text: "Совершенный атеист стоит на предпоследней верхней ступени до совершеннейшей веры.", note: "Парадокс Шатова — вернуться к этому в эссе." },
      { id: "q2", page: 96, text: "Человек несчастен потому, что не знает, что он счастлив.", note: "" },
    ],
    links: ["c2", "n1", "m7"], note: "Читаю параллельно с курсом по русской мысли." },
  { id: "m2", type: "science", title: "Структура научных революций", author: "Т. Кун", cover: CV.cobalt,
    status: "reading", rating: 5, tags: ["философия науки", "парадигма"],
    progress: { current: 134, total: 212, unit: "стр." },
    timeLog: [{ d: 1, min: 50 }, { d: 2, min: 40 }, { d: 3, min: 25 }],
    quotes: [ { id: "q3", page: 84, text: "Парадигмы получают свой статус потому, что более успешны в решении проблем.", note: "Ключ к 3 главе." } ],
    links: ["c1", "n2"], note: "" },
  { id: "m3", type: "video", title: "Капитал. Лекция 4: Товарный фетишизм", author: "Д. Харви", cover: CV.signal,
    status: "reading", rating: 4, tags: ["марксизм", "лекция"],
    progress: { current: 38, total: 92, unit: "мин" },
    timeLog: [{ d: 3, min: 38 }],
    quotes: [], links: ["c2"], note: "Серия из 13 лекций." },
  { id: "m4", type: "science", title: "Думай медленно… решай быстро", author: "Д. Канеман", cover: CV.mustard,
    status: "done", rating: 5, tags: ["психология", "когнитивистика"],
    progress: { current: 656, total: 656, unit: "стр." },
    timeLog: [{ d: 0, min: 60 }],
    quotes: [ { id: "q4", page: 21, text: "Система 1 работает быстро и автоматически, почти без усилий.", note: "" } ],
    links: ["n3"], note: "Перечитать главы про эвристики." },
  { id: "m5", type: "fiction", title: "Невыносимая лёгкость бытия", author: "М. Кундера", cover: CV.plum,
    status: "queue", rating: 0, tags: ["роман", "XX век"],
    progress: { current: 0, total: 320, unit: "стр." }, timeLog: [], quotes: [], links: [], note: "" },
  { id: "m6", type: "video", title: "Crash Course: История идей", author: "—", cover: CV.teal,
    status: "paused", rating: 3, tags: ["видео", "обзор"],
    progress: { current: 6, total: 24, unit: "сер." }, timeLog: [], quotes: [], links: [], note: "Поставил на паузу." },
  { id: "m7", type: "science", title: "Происхождение видов", author: "Ч. Дарвин", cover: CV.forest,
    status: "reading", rating: 4, tags: ["биология", "XIX век"],
    progress: { current: 90, total: 502, unit: "стр." }, timeLog: [{ d: 2, min: 45 }], quotes: [], links: ["m1"], note: "" },
  { id: "m8", type: "fiction", title: "Сто лет одиночества", author: "Г. Г. Маркес", cover: CV.rust,
    status: "done", rating: 5, tags: ["роман", "магреализм"],
    progress: { current: 417, total: 417, unit: "стр." }, timeLog: [],
    quotes: [ { id: "q5", page: 11, text: "Мир был так нов, что многие вещи не имели названия.", note: "Лучшее начало." } ],
    links: [], note: "" },
  { id: "m9", type: "science", title: "Краткая история времени", author: "С. Хокинг", cover: CV.slate,
    status: "queue", rating: 0, tags: ["физика", "космология"],
    progress: { current: 0, total: 256, unit: "стр." }, timeLog: [], quotes: [], links: [], note: "" },
  { id: "m10", type: "video", title: "MIT 6.006: Алгоритмы", author: "E. Demaine", cover: CV.olive,
    status: "reading", rating: 5, tags: ["cs", "лекция"],
    progress: { current: 9, total: 47, unit: "лек" }, timeLog: [{ d: 1, min: 60 }, { d: 3, min: 45 }], quotes: [], links: ["c3"], note: "" },
  { id: "m11", type: "fiction", title: "1984", author: "Дж. Оруэлл", cover: CV.ink,
    status: "done", rating: 5, tags: ["роман", "антиутопия", "политика"],
    progress: { current: 328, total: 328, unit: "стр." }, timeLog: [], quotes: [], links: ["m1"], note: "" },
  { id: "m12", type: "science", title: "Sapiens. Краткая история человечества", author: "Ю. Н. Харари", cover: CV.clay,
    status: "paused", rating: 4, tags: ["история", "антропология"],
    progress: { current: 180, total: 498, unit: "стр." }, timeLog: [{ d: 0, min: 30 }], quotes: [], links: [], note: "" },
];

/* ---- courses (timeLog per module) ----------------------------- */
const COURSES = [
  { id: "c1", title: "Философия науки", cover: CV.cobalt, tags: ["философия", "наука"],
    links: ["m2"], desc: "Самостоятельный курс по эпистемологии: от позитивизма до Куна и Фейерабенда.",
    ptsRates: { firstHour: 50, first10h: 30, normal: 15 },
    modules: [
      { id: "c1m1", title: "Позитивизм и его критики",
        timeLog: [{ d: 0, min: 90 }, { d: 1, min: 60 }],
        tasks: [
          { id: "c1t1", title: "Конспект: Венский кружок", done: true, pts: 30 },
          { id: "c1t2", title: "Эссе на 600 слов: верификация vs фальсификация", done: true, pts: 60 },
        ]},
      { id: "c1m2", title: "Парадигмы (Кун)",
        timeLog: [{ d: 3, min: 90 }],
        tasks: [
          { id: "c1t3", title: "Прочитать гл. 3–5 «Структуры…»", done: false, pts: 40 },
          { id: "c1t4", title: "Карта понятий: нормальная наука → кризис", done: false, pts: 35 },
          { id: "c1t5", title: "Тест по главам 1–5", done: false, pts: 25 },
        ]},
      { id: "c1m3", title: "Анархизм метода (Фейерабенд)",
        timeLog: [],
        tasks: [
          { id: "c1t6", title: "Прочитать «Против метода», введение", done: false, pts: 30 },
        ]},
    ]},
  { id: "c2", title: "Русская мысль XIX века", cover: CV.oxblood, tags: ["философия", "литература"],
    links: ["m1", "m3"], desc: "Литература и идеи: от славянофилов до богоискательства.",
    ptsRates: { firstHour: 50, first10h: 30, normal: 15 },
    modules: [
      { id: "c2m1", title: "Почвенничество",
        timeLog: [{ d: 2, min: 45 }],
        tasks: [
          { id: "c2t1", title: "Достоевский: «Бесы», ч. 1", done: true, pts: 45 },
          { id: "c2t2", title: "Заметка: идея «беса» как метафора", done: false, pts: 25 },
        ]},
      { id: "c2m2", title: "Западники и славянофилы",
        timeLog: [{ d: 3, min: 30 }],
        tasks: [
          { id: "c2t3", title: "Сравнительная таблица позиций", done: false, pts: 30 },
        ]},
    ]},
  { id: "c3", title: "Алгоритмы и структуры данных", cover: CV.olive, tags: ["cs"],
    links: ["m10"], desc: "Параллельно с курсом MIT 6.006.",
    ptsRates: { firstHour: 50, first10h: 30, normal: 15 },
    modules: [
      { id: "c3m1", title: "Сортировки",
        timeLog: [{ d: 1, min: 30 }],
        tasks: [
          { id: "c3t1", title: "Реализовать merge sort", done: true, pts: 40 },
          { id: "c3t2", title: "Разбор асимптотики O(n log n)", done: true, pts: 20 },
        ]},
      { id: "c3m2", title: "Хеш-таблицы",
        timeLog: [{ d: 3, min: 45 }],
        tasks: [
          { id: "c3t3", title: "Лекция 9 + конспект", done: false, pts: 30 },
          { id: "c3t4", title: "Задачи на коллизии", done: false, pts: 35 },
        ]},
    ]},
  { id: "cwork", title: "Работа", cover: CV.slate, tags: ["работа"], isWork: true,
    links: [], desc: "Учёт рабочего времени. Цель: 15 часов в неделю.",
    weekTarget: 15 * 3600,
    ptsRates: { firstHour: 50, first10h: 30, normal: 15 },
    timeLog: [],
    modules: [
      { id: "cwm1", title: "Основная работа",
        timeLog: [{ d: 0, sec: 3 * 3600 }, { d: 1, sec: 2.5 * 3600 }, { d: 2, sec: 3.5 * 3600 }, { d: 3, sec: 1.5 * 3600 }],
        tasks: [] },
    ]},
];

/* ---- today's tasks (planner) ---------------------------------- */
const TASKS = [
  { id: "t1", title: "Прочитать гл. 3 «Структуры научных революций»", desc: "Главы про аномалии и кризис парадигмы. Выписать ключевые тезисы.", pts: 40, done: false,
    time: "09:00", dur: 60, kind: "material", linkId: "m2", tags: ["курс"], timeLog: [{ d: 3, min: 25 }],
    notes: [{ id: "tn1", text: "Дочитал до раздела про нормальную науку — пока всё ясно.", date: "10.06" }] },
  { id: "t2", title: "Эссе: верификация vs фальсификация — финал", desc: "Финальная правка: вычитка, ссылки, форматирование. Сдать в конце дня.", pts: 60, done: true,
    time: "10:30", dur: 90, kind: "course", linkId: "c1", tags: ["курс", "письмо"], timeLog: [{ d: 2, min: 60 }, { d: 3, min: 90 }],
    notes: [{ id: "tn2", text: "Переписал вывод — добавил про Лакатоса.", date: "10.06" }, { id: "tn3", text: "Готово, отправил.", date: "11.06" }] },
  { id: "t3", title: "Лекция Харви №4: товарный фетишизм", desc: "", pts: 30, done: false,
    time: "14:00", dur: 60, kind: "material", linkId: "m3", tags: ["видео"], timeLog: [], notes: [] },
  { id: "t4", title: "Карта понятий: нормальная наука → кризис", desc: "Сделать визуальную карту на бумаге или в Miro.", pts: 35, done: false,
    time: null, dur: 45, kind: "course", linkId: "c1", tags: ["курс"], timeLog: [], notes: [] },
  { id: "t5", title: "Выписать 3 цитаты из «Бесов»", desc: "", pts: 20, done: false,
    time: null, dur: 30, kind: "material", linkId: "m1", tags: ["заметки"], timeLog: [{ d: 3, min: 15 }], notes: [] },
  { id: "t6", title: "Merge sort: дописать тесты", desc: "Граничные случаи: пустой массив, один элемент, уже отсортированный.", pts: 25, done: false,
    time: "16:30", dur: 45, kind: "course", linkId: "c3", tags: ["cs"], timeLog: [{ d: 1, min: 30 }], notes: [] },
  { id: "t7", title: "Прогулка + аудиокнига 30 мин", desc: "", pts: 15, done: true,
    time: null, dur: 30, kind: "personal", linkId: null, tags: ["личное"], timeLog: [{ d: 3, min: 30 }], notes: [] },
  { id: "t8", title: "Обзор недели и план на завтра", desc: "Что сделано, что нет, приоритеты на пятницу.", pts: 20, done: false,
    time: "21:00", dur: 30, kind: "personal", linkId: null, tags: ["рутина"], timeLog: [], notes: [] },
];

/* ---- habits --------------------------------------------------- */
const HABITS = [
  { id: "h1", name: "Чтение 30+ минут", kind: "good", pts: 20, icon: "book",
    days: [true, true, true, false, false, false, false] },
  { id: "h2", name: "Зарядка утром", kind: "good", pts: 15, icon: "run",
    days: [true, false, true, false, false, false, false] },
  { id: "h3", name: "Без соцсетей до обеда", kind: "good", pts: 15, icon: "shield",
    days: [true, true, false, false, false, false, false] },
  { id: "h4", name: "Вода 2 литра", kind: "good", pts: 10, icon: "drop",
    days: [true, true, true, true, false, false, false] },
  { id: "h5", name: "Поздний отбой (после 1:00)", kind: "bad", pts: 20, icon: "moon",
    days: [false, true, false, false, false, false, false] },
  { id: "h6", name: "Фастфуд", kind: "bad", pts: 15, icon: "burger",
    days: [false, false, true, false, false, false, false] },
];

/* ---- achievements (repeatable goals) -------------------------- */
const ACHIEVEMENTS = [
  { id: "a1", name: "Первая сотня страниц", pts: 50, img: null, cover: CV.mustard, completions: [{ d: 0, date: "08.06" }] },
  { id: "a2", name: "Неделя без срывов", pts: 100, img: null, cover: CV.forest, completions: [{ d: 2, date: "10.06" }] },
  { id: "a3", name: "Эссе по философии науки сдано", pts: 75, img: null, cover: CV.signal, completions: [{ d: 3, date: "11.06" }] },
];

/* ---- notes ---------------------------------------------------- */
const NOTES = [
  { id: "n1", title: "Бес как метафора идеи", pinned: true, hand: false,
    body: "Достоевский показывает идею, овладевающую человеком, как одержимость. «Бесы» = заражённые идеями. Связать с теорией мемов и с Куном: идея-парадигма тоже «овладевает» сообществом.",
    tags: ["литература", "идеи"], links: ["m1", "c2"], date: "09.06" },
  { id: "n2", title: "Парадигма ≠ теория", pinned: false, hand: false,
    body: "Кун: парадигма шире теории — это образец решения задач + ценности + методы. Важно не путать в эссе.",
    tags: ["наука"], links: ["m2", "c1"], date: "08.06" },
  { id: "n3", title: "Две системы мышления", pinned: false, hand: true,
    body: "Система 1 — быстрая, автоматическая. Система 2 — медленная, усилие. Большинство ошибок — когда С1 отвечает за С2.",
    tags: ["психология"], links: ["m4"], date: "05.06" },
  { id: "n4", title: "План эссе по философии науки", pinned: true, hand: false,
    body: "1) Проблема демаркации. 2) Поппер: фальсификация. 3) Кун: аномалии и революции. 4) Синтез: можно ли совместить?",
    tags: ["план", "письмо"], links: ["c1"], date: "10.06" },
  { id: "n5", title: "Идея на потом", pinned: false, hand: true,
    body: "Сделать граф всех прочитанных книг по десятилетиям написания — посмотреть на «карту» своего чтения.",
    tags: ["идеи"], links: [], date: "07.06" },
  { id: "n6", title: "Цитата для эпиграфа", pinned: false, hand: false,
    body: "«Мир был так нов, что многие вещи не имели названия» — Маркес. Подходит к разделу про язык науки.",
    tags: ["цитаты"], links: ["m8"], date: "06.06" },
];

/* ---- points history (Mon..Sun this week) ---------------------- */
const HISTORY = [
  { day: "Пн", taskPts: 180, habitPts: 60 },
  { day: "Вт", taskPts: 240, habitPts: 40 },
  { day: "Ср", taskPts: 210, habitPts: 35 },
  { day: "Чт", taskPts: 0,   habitPts: 0 },  // today — computed live
  { day: "Пт", taskPts: 0,   habitPts: 0 },
  { day: "Сб", taskPts: 0,   habitPts: 0 },
  { day: "Вс", taskPts: 0,   habitPts: 0 },
];

/* ---- mood / state journal (−5..+5 per dimension) -------------- */
const MOOD_QUESTIONS = [
  { id: "happiness",      label: "Счастье",             lo: "Тяжело",      hi: "Счастье" },
  { id: "tiredness",      label: "Усталость",           lo: "Бодрость",    hi: "Истощение" },
  { id: "anxiety",        label: "Тревога",             lo: "Спокойствие", hi: "Тревога" },
  { id: "dissatisfaction",label: "Неудовлетворённость", lo: "Доволен",     hi: "Недоволен" },
  { id: "creativity",     label: "Творчество",          lo: "Пусто",       hi: "Подъём" },
  { id: "work",           label: "Работа",              lo: "Простой",     hi: "Продуктивно" },
  { id: "future",         label: "Будущее",             lo: "Мрачно",      hi: "Надежда" },
];
const MOODS = {
  0: { happiness: 2, tiredness: -1, anxiety: -2, dissatisfaction: -1, creativity: 3, work: 2, future: 1 },
  1: { happiness: 3, tiredness: -2, anxiety: -1, dissatisfaction: 0,  creativity: 2, work: 3, future: 2 },
  2: { happiness: 1, tiredness: -3, anxiety: -2, dissatisfaction: -2, creativity: 1, work: 1, future: 0 },
  3: { happiness: 2, tiredness: -2, anxiety: -1, dissatisfaction: -1, creativity: 2, work: 2, future: 1 },
};

/* (work is now a regular course — see COURSES) */

const EMPTY_WEEK = [false, false, false, false, false, false, false];
const todayDate = new Date();
const todayWeekday = (todayDate.getDay() + 6) % 7;
const todayLabel = todayDate.toLocaleDateString("ru-RU", { weekday: "short", day: "numeric", month: "long", year: "numeric" });
const emptyHistory = Array.from({ length: 7 }, () => ({ taskPts: 0, habitPts: 0 }));

const SEED = {
  materials: [], courses: [], tasks: [], habits: [],
  notes: [], history: emptyHistory, moods: {}, achievements: [],
  activityLog: [],
  targets: { day: 100, week: 500 },
  timer: null,
  todayIdx: todayWeekday, dateLabel: todayLabel,
};

window.CV = CV;
window.SEED = SEED;
window.WEEK_DATES = WEEK_DATES;
window.DAY_NAMES = DAY_NAMES;
window.MOOD_QUESTIONS = MOOD_QUESTIONS;

/* ---- time helpers (everything is stored in SECONDS) ----------- */
window.entrySec = (e) => (e.sec != null ? e.sec : (e.min || 0) * 60);
window.timeToday = (log, idx) => (log || []).filter((e) => e.d === idx).reduce((s, e) => s + window.entrySec(e), 0);
window.timeWeek = (log) => (log || []).reduce((s, e) => s + window.entrySec(e), 0);
/* compact human duration: "1д 2ч 5м", seconds only for short spans */
window.fmtDur = (sec) => {
  sec = Math.round(sec || 0);
  if (sec <= 0) return "0с";
  const d = Math.floor(sec / 86400), h = Math.floor((sec % 86400) / 3600), m = Math.floor((sec % 3600) / 60), s = sec % 60;
  const parts = [];
  if (d) parts.push(d + "д");
  if (h) parts.push(h + "ч");
  if (m) parts.push(m + "м");
  if (s && !d && !h) parts.push(s + "с");
  return parts.length ? parts.join(" ") : s + "с";
};
/* stopwatch clock: (Dd) HH:MM:SS */
window.fmtClock = (sec) => {
  sec = Math.max(0, Math.round(sec || 0));
  const d = Math.floor(sec / 86400), h = Math.floor((sec % 86400) / 3600), m = Math.floor((sec % 3600) / 60), s = sec % 60;
  const p = (n) => String(n).padStart(2, "0");
  return (d ? d + "д " : "") + p(h) + ":" + p(m) + ":" + p(s);
};
window.fmtMin = window.fmtDur; /* back-compat: callers now pass seconds */

/* ---- auth ----------------------------------------------------- */
window.AuthToken = localStorage.getItem("planer-token") || null;
window.AuthUser = null;

window.authFetch = function (url, opts = {}) {
  const headers = { ...(opts.headers || {}) };
  if (window.AuthToken) headers["X-Auth-Token"] = window.AuthToken;
  return fetch(url, { ...opts, headers });
};

window.authLogin = async function (login, password) {
  const res = await fetch("/api/login", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ login, password }),
  });
  const data = await res.json();
  if (!res.ok) throw new Error(data.error || "login failed");
  localStorage.setItem("planer-token", data.token);
  window.AuthToken = data.token;
  window.AuthUser = data.user;
  return data;
};

window.authLogout = function () {
  window.authFetch("/api/logout", { method: "POST" }).catch(() => {});
  localStorage.removeItem("planer-token");
  localStorage.removeItem("planer-state");
  window.AuthToken = null;
  window.AuthUser = null;
  window.location.reload();
};

window.authCheck = async function () {
  if (!window.AuthToken) return null;
  try {
    const res = await window.authFetch("/api/me");
    if (!res.ok) { window.AuthToken = null; localStorage.removeItem("planer-token"); return null; }
    const data = await res.json();
    window.AuthUser = data.user;
    return data.user;
  } catch { return null; }
};

/* ---- store ---------------------------------------------------- */
const SEED_VERSION = "planer-v11";
function loadStateLocal() {
  try {
    const raw = localStorage.getItem("planer-state");
    if (raw) {
      const p = JSON.parse(raw);
      if (p.__v === SEED_VERSION) return p.state;
    }
  } catch (e) {}
  return null;
}

let _saveTimer = null;
function syncActivityLog(state) {
  const today = new Date().toISOString().slice(0, 10);
  const day = window.computeDay(state);
  const log = state.activityLog || [];
  const idx = log.findIndex((e) => e.date === today);
  if (idx >= 0) {
    if (log[idx].pts === day.net) return state;
    const updated = log.slice();
    updated[idx] = { date: today, pts: day.net };
    return { ...state, activityLog: updated };
  }
  return { ...state, activityLog: [...log, { date: today, pts: day.net }] };
}
function saveToServer(state) {
  clearTimeout(_saveTimer);
  _saveTimer = setTimeout(() => {
    const synced = syncActivityLog(state);
    if (synced !== state) {
      state = synced;
      window.PlannerStore._replace(synced);
      try { localStorage.setItem("planer-state", JSON.stringify({ __v: SEED_VERSION, state: synced })); } catch (e) {}
    }
    window.authFetch("/api/state", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ version: SEED_VERSION, state }),
    }).catch(() => {});
  }, 400);
}

function makeStore(initial) {
  let state = initial;
  const subs = new Set();
  return {
    get: () => state,
    set: (updater) => {
      state = typeof updater === "function" ? updater(state) : { ...state, ...updater };
      try { localStorage.setItem("planer-state", JSON.stringify({ __v: SEED_VERSION, state })); } catch (e) {}
      saveToServer(state);
      subs.forEach((f) => f());
    },
    subscribe: (f) => { subs.add(f); return () => subs.delete(f); },
    _replace: (s) => { state = s; subs.forEach((f) => f()); },
  };
}

window.PlannerStore = makeStore(loadStateLocal() || JSON.parse(JSON.stringify(SEED)));

window.loadServerState = function () {
  window.authFetch("/api/state").then((r) => r.json()).then((res) => {
    if (res.state && res.version === SEED_VERSION) {
      try { localStorage.setItem("planer-state", JSON.stringify({ __v: SEED_VERSION, state: res.state })); } catch (e) {}
      window.PlannerStore._replace(res.state);
    }
  }).catch(() => {});
};

window.useStore = function () {
  const store = window.PlannerStore;
  const [, force] = React.useState(0);
  React.useEffect(() => store.subscribe(() => force((n) => n + 1)), []);
  return store.get();
};

/* ---- course time → points (tiered) ---------------------------- */
const DEFAULT_RATES = { firstHour: 50, first10h: 30, normal: 15 };
window.DEFAULT_RATES = DEFAULT_RATES;

window.courseTimePts = function (totalSec, rates) {
  const r = rates || DEFAULT_RATES;
  const hours = totalSec / 3600;
  if (hours <= 0) return 0;
  let pts = 0;
  if (hours <= 1) {
    pts = hours * r.firstHour;
  } else if (hours <= 10) {
    pts = 1 * r.firstHour + (hours - 1) * r.first10h;
  } else {
    pts = 1 * r.firstHour + 9 * r.first10h + (hours - 10) * r.normal;
  }
  return Math.round(pts);
};

/* aggregate all module timeLogs for a course */
window.courseTimeLog = function (course) {
  return [...(course.timeLog || []), ...(course.modules || []).flatMap((m) => m.timeLog || [])];
};

window.courseTimePtsToday = function (course, todayIdx) {
  const r = course.ptsRates || DEFAULT_RATES;
  let pts = 0;
  (course.modules || []).forEach((mod) => {
    const log = mod.timeLog || [];
    const before = log.filter((e) => e.d < todayIdx).reduce((s, e) => s + window.entrySec(e), 0);
    const total = log.reduce((s, e) => s + window.entrySec(e), 0);
    pts += window.courseTimePts(total, r) - window.courseTimePts(before, r);
  });
  const cLog = course.timeLog || [];
  const cToday = cLog.filter((e) => e.d === todayIdx).reduce((s, e) => s + window.entrySec(e), 0);
  pts += Math.round((cToday / 3600) * 15);
  return pts;
};

/* ---- derived: compute today's points -------------------------- */
window.computeDay = function (state) {
  const t = state.targets;
  const taskPts = state.tasks.filter((x) => x.done).reduce((s, x) => s + x.pts, 0);
  let habitPts = 0;
  const i = state.todayIdx;
  state.habits.forEach((h) => { if (h.days[i]) habitPts += h.kind === "good" ? h.pts : -h.pts; });
  let coursePts = 0;
  state.courses.forEach((c) => { coursePts += window.courseTimePtsToday(c, i); });
  let achPts = 0;
  (state.achievements || []).forEach((a) => {
    (a.completions || []).forEach((c) => { if (c.d === i) achPts += (a.pts || 0); });
  });
  return {
    taskPts, habitPts, coursePts, achPts, net: taskPts + habitPts + coursePts + achPts,
    target: t.day,
  };
};

/* ---- actions -------------------------------------------------- */
let toastSeq = 0;
window.PlannerActions = {
  toggleTask(id) {
    let toast = null;
    window.PlannerStore.set((s) => {
      const tasks = s.tasks.map((t) => {
        if (t.id !== id) return t;
        const done = !t.done;
        toast = { id: ++toastSeq, text: done ? "Задача выполнена" : "Снята отметка", pts: done ? t.pts : -t.pts };
        return { ...t, done };
      });
      return { ...s, tasks };
    });
    if (toast) window.pushToast(toast);
  },
  toggleHabitToday(id) {
    let toast = null;
    window.PlannerStore.set((s) => {
      const i = s.todayIdx;
      const habits = s.habits.map((h) => {
        if (h.id !== id) return h;
        const days = h.days.slice();
        days[i] = !days[i];
        const delta = h.kind === "good" ? (days[i] ? h.pts : -h.pts) : (days[i] ? -h.pts : h.pts);
        toast = { id: ++toastSeq, text: days[i] ? h.name : "Отметка снята", pts: delta };
        return { ...h, days };
      });
      return { ...s, habits };
    });
    if (toast) window.pushToast(toast);
  },
  toggleHabitDay(id, dayIdx) {
    window.PlannerStore.set((s) => ({
      ...s,
      habits: s.habits.map((h) => {
        if (h.id !== id) return h;
        const days = h.days.slice();
        days[dayIdx] = !days[dayIdx];
        return { ...h, days };
      }),
    }));
  },
  removeTask(id) {
    window.PlannerStore.set((s) => ({ ...s, tasks: s.tasks.filter((t) => t.id !== id) }));
    window.pushToast({ id: ++toastSeq, text: "Задача удалена", pts: 0 });
  },
  removeHabit(id) {
    window.PlannerStore.set((s) => ({ ...s, habits: s.habits.filter((h) => h.id !== id) }));
    window.pushToast({ id: ++toastSeq, text: "Привычка удалена", pts: 0 });
  },
  addHabit(habit) {
    const id = "h" + (Date.now() % 100000);
    window.PlannerStore.set((s) => ({
      ...s,
      habits: [...s.habits, { id, days: [false, false, false, false, false, false, false], ...habit }],
    }));
    window.pushToast({ id: ++toastSeq, text: "Привычка добавлена", pts: 0 });
  },
  removeMaterial(id) {
    window.PlannerStore.set((s) => ({ ...s, materials: s.materials.filter((m) => m.id !== id) }));
    window.pushToast({ id: ++toastSeq, text: "Материал удалён", pts: 0 });
  },
  scheduleTask(id, time, timeEnd) {
    window.PlannerStore.set((s) => ({ ...s, tasks: s.tasks.map((t) => {
      if (t.id !== id) return t;
      const upd = { ...t, time };
      if (timeEnd !== undefined) upd.timeEnd = timeEnd;
      if (time && timeEnd) {
        const [h1, m1] = time.split(":").map(Number);
        const [h2, m2] = timeEnd.split(":").map(Number);
        const diff = (h2 * 60 + m2) - (h1 * 60 + m1);
        if (diff > 0) upd.dur = diff;
      }
      return upd;
    }) }));
  },
  unscheduleTask(id) {
    window.PlannerStore.set((s) => ({ ...s, tasks: s.tasks.map((t) => (t.id === id ? { ...t, time: null, timeEnd: null } : t)) }));
  },
  addTask(task) {
    const id = "t" + (Date.now() % 100000);
    window.PlannerStore.set((s) => ({ ...s, tasks: [{ id, done: false, dur: 45, tags: [], timeLog: [], notes: [], desc: "", ...task }, ...s.tasks] }));
    window.pushToast({ id: ++toastSeq, text: "Задача добавлена", pts: task.pts || 0 });
  },
  updateTaskDesc(id, desc) {
    window.PlannerStore.set((s) => ({ ...s, tasks: s.tasks.map((t) => (t.id === id ? { ...t, desc } : t)) }));
  },
  addTaskNote(taskId, text) {
    if (!text || !text.trim()) return;
    const noteId = "tn" + (Date.now() % 100000);
    const date = new Date().toLocaleDateString("ru-RU", { day: "2-digit", month: "2-digit" });
    window.PlannerStore.set((s) => ({
      ...s,
      tasks: s.tasks.map((t) => t.id === taskId ? { ...t, notes: [...(t.notes || []), { id: noteId, text: text.trim(), date }] } : t),
    }));
    window.pushToast({ id: ++toastSeq, text: "Заметка добавлена", pts: 0 });
  },
  removeTaskNote(taskId, noteId) {
    window.PlannerStore.set((s) => ({
      ...s,
      tasks: s.tasks.map((t) => t.id === taskId ? { ...t, notes: (t.notes || []).filter((n) => n.id !== noteId) } : t),
    }));
  },
  toggleCourseTask(courseId, taskId) {
    let toast = null;
    window.PlannerStore.set((s) => ({
      ...s,
      courses: s.courses.map((c) => {
        if (c.id !== courseId) return c;
        return {
          ...c,
          modules: c.modules.map((m) => ({
            ...m,
            tasks: m.tasks.map((t) => {
              if (t.id !== taskId) return t;
              const done = !t.done;
              toast = { id: ++toastSeq, text: done ? "Шаг курса выполнен" : "Снята отметка", pts: done ? t.pts : -t.pts };
              return { ...t, done };
            }),
          })),
        };
      }),
    }));
    if (toast) window.pushToast(toast);
  },
  /* time tracking ------------------------------------------------ */
  logTime(kind, id, sec, courseId) {
    sec = Math.round(sec || 0);
    if (!sec) return;
    window.PlannerStore.set((s) => {
      const entry = { d: s.todayIdx, sec };
      const apply = (arr) => arr.map((x) => (x.id === id ? { ...x, timeLog: [...(x.timeLog || []), entry] } : x));
      if (kind === "task") return { ...s, tasks: apply(s.tasks) };
      if (kind === "material") return { ...s, materials: apply(s.materials) };
      if (kind === "module") {
        return { ...s, courses: s.courses.map((c) => {
          if (c.id !== courseId) return c;
          return { ...c, modules: c.modules.map((m) => m.id === id ? { ...m, timeLog: [...(m.timeLog || []), entry] } : m) };
        })};
      }
      if (kind === "course") {
        return { ...s, courses: s.courses.map((c) => c.id === id ? { ...c, timeLog: [...(c.timeLog || []), entry] } : c) };
      }
      return s;
    });
    window.pushToast({ id: ++toastSeq, text: "Записано: " + window.fmtDur(sec), pts: 0 });
  },
  /* stopwatch: only one running at a time -------------------------- */
  startTimer(kind, id, courseId) {
    const s = window.PlannerStore.get();
    if (s.timer) {
      if (s.timer.kind === kind && s.timer.id === id) return;
      window.PlannerActions.stopTimer();
    }
    window.PlannerStore.set((st) => ({ ...st, timer: { kind, id, courseId: courseId || null, at: Date.now() } }));
    window.pushToast({ id: ++toastSeq, text: "Секундомер запущен", pts: 0 });
  },
  stopTimer() {
    const s = window.PlannerStore.get();
    const tm = s.timer;
    if (!tm) return;
    const sec = Math.max(1, Math.round((Date.now() - tm.at) / 1000));
    window.PlannerStore.set((st) => {
      const entry = { d: st.todayIdx, sec };
      const apply = (arr) => arr.map((x) => (x.id === tm.id ? { ...x, timeLog: [...(x.timeLog || []), entry] } : x));
      const n = { ...st, timer: null };
      if (tm.kind === "task") n.tasks = apply(st.tasks);
      if (tm.kind === "material") n.materials = apply(st.materials);
      if (tm.kind === "module") {
        n.courses = st.courses.map((c) => {
          if (c.id !== tm.courseId) return c;
          return { ...c, modules: c.modules.map((m) => m.id === tm.id ? { ...m, timeLog: [...(m.timeLog || []), entry] } : m) };
        });
      }
      if (tm.kind === "course") {
        n.courses = st.courses.map((c) => c.id === tm.id ? { ...c, timeLog: [...(c.timeLog || []), entry] } : c);
      }
      return n;
    });
    window.pushToast({ id: ++toastSeq, text: "Записано: " + window.fmtDur(sec), pts: 0 });
  },
  /* mood / state ------------------------------------------------- */
  setMood(field, val) {
    window.PlannerStore.set((s) => {
      const i = s.todayIdx;
      return { ...s, moods: { ...s.moods, [i]: { ...(s.moods[i] || {}), [field]: val } } };
    });
  },
  /* targets / plan ----------------------------------------------- */
  setTarget(key, val) {
    window.PlannerStore.set((s) => ({ ...s, targets: { ...s.targets, [key]: Math.max(0, Math.round(val)) } }));
  },
  /* library ------------------------------------------------------ */
  addMaterial(mat) {
    const id = "m" + (Date.now() % 100000);
    window.PlannerStore.set((s) => ({
      ...s,
      materials: [{ id, status: "queue", rating: 0, tags: [], quotes: [], links: [], note: "", timeLog: [],
        progress: { current: 0, total: mat.total || 100, unit: mat.unit || "стр." }, ...mat }, ...s.materials],
    }));
    window.pushToast({ id: ++toastSeq, text: "Материал добавлен в библиотеку", pts: 0 });
  },
  updateProgress(id, current) {
    window.PlannerStore.set((s) => ({
      ...s,
      materials: s.materials.map((m) => {
        if (m.id !== id) return m;
        const cur = Math.max(0, Math.min(current, m.progress.total));
        const status = cur >= m.progress.total ? "done" : cur > 0 ? "reading" : "queue";
        return { ...m, progress: { ...m.progress, current: cur }, status };
      }),
    }));
  },
  setRating(id, rating) {
    window.PlannerStore.set((s) => ({ ...s, materials: s.materials.map((m) => (m.id === id ? { ...m, rating } : m)) }));
  },
  setMaterialCoverImg(id, dataUrl) {
    window.PlannerStore.set((s) => ({ ...s, materials: s.materials.map((m) => (m.id === id ? { ...m, coverImg: dataUrl } : m)) }));
    window.pushToast({ id: ++toastSeq, text: "Обложка обновлена", pts: 0 });
  },
  setCourseCoverImg(id, dataUrl) {
    window.PlannerStore.set((s) => ({ ...s, courses: s.courses.map((c) => (c.id === id ? { ...c, coverImg: dataUrl } : c)) }));
    window.pushToast({ id: ++toastSeq, text: "Обложка курса обновлена", pts: 0 });
  },
  addQuote(id, quote) {
    window.PlannerStore.set((s) => ({
      ...s,
      materials: s.materials.map((m) =>
        m.id === id ? { ...m, quotes: [...m.quotes, { id: "q" + Date.now(), note: "", ...quote }] } : m),
    }));
    window.pushToast({ id: ++toastSeq, text: "Цитата сохранена", pts: 0 });
  },
  /* achievements ------------------------------------------------- */
  addAchievement(a) {
    const id = "a" + (Date.now() % 100000);
    const palette = Object.values(window.CV);
    const cover = palette[Math.floor(Math.random() * palette.length)];
    window.PlannerStore.set((s) => ({
      ...s,
      achievements: [{ id, img: null, cover, completions: [], ...a }, ...(s.achievements || [])],
    }));
    window.pushToast({ id: ++toastSeq, text: "Достижение добавлено", pts: 0 });
  },
  completeAchievement(id) {
    const date = new Date().toLocaleDateString("ru-RU", { day: "2-digit", month: "2-digit" });
    let pts = 0;
    window.PlannerStore.set((s) => ({
      ...s,
      achievements: (s.achievements || []).map((a) => {
        if (a.id !== id) return a;
        pts = a.pts || 0;
        return { ...a, completions: [...(a.completions || []), { d: s.todayIdx, date }] };
      }),
    }));
    if (pts) window.pushToast({ id: ++toastSeq, text: "Достижение выполнено!", pts });
  },
  addCourse(course) {
    const id = "c" + (Date.now() % 100000);
    const palette = Object.values(window.CV);
    const cover = palette[Math.floor(Math.random() * palette.length)];
    window.PlannerStore.set((s) => ({
      ...s,
      courses: [...s.courses, { id, cover, links: [], desc: "", ptsRates: { ...DEFAULT_RATES }, timeLog: [], modules: [], ...course }],
    }));
    window.pushToast({ id: ++toastSeq, text: "Курс создан", pts: 0 });
    return id;
  },
  removeCourse(courseId) {
    window.PlannerStore.set((s) => ({ ...s, courses: s.courses.filter((c) => c.id !== courseId) }));
    window.pushToast({ id: ++toastSeq, text: "Курс удалён", pts: 0 });
  },
  addModule(courseId, title) {
    const modId = "m" + (Date.now() % 100000);
    window.PlannerStore.set((s) => ({
      ...s,
      courses: s.courses.map((c) => {
        if (c.id !== courseId) return c;
        return { ...c, modules: [...c.modules, { id: modId, title, timeLog: [], tasks: [] }] };
      }),
    }));
    window.pushToast({ id: ++toastSeq, text: "Модуль добавлен", pts: 0 });
  },
  removeModule(courseId, moduleId) {
    window.PlannerStore.set((s) => ({
      ...s,
      courses: s.courses.map((c) => {
        if (c.id !== courseId) return c;
        return { ...c, modules: c.modules.filter((m) => m.id !== moduleId) };
      }),
    }));
    window.pushToast({ id: ++toastSeq, text: "Модуль удалён", pts: 0 });
  },
  addCourseTask(courseId, moduleId, title, pts) {
    const taskId = "ct" + (Date.now() % 100000);
    window.PlannerStore.set((s) => ({
      ...s,
      courses: s.courses.map((c) => {
        if (c.id !== courseId) return c;
        return { ...c, modules: c.modules.map((m) => {
          if (m.id !== moduleId) return m;
          return { ...m, tasks: [...m.tasks, { id: taskId, title, done: false, pts: pts || 20 }] };
        })};
      }),
    }));
    window.pushToast({ id: ++toastSeq, text: "Шаг добавлен", pts: 0 });
  },
  removeCourseTask(courseId, moduleId, taskId) {
    window.PlannerStore.set((s) => ({
      ...s,
      courses: s.courses.map((c) => {
        if (c.id !== courseId) return c;
        return { ...c, modules: c.modules.map((m) => {
          if (m.id !== moduleId) return m;
          return { ...m, tasks: m.tasks.filter((t) => t.id !== taskId) };
        })};
      }),
    }));
  },
  setWorkTarget(courseId, sec) {
    window.PlannerStore.set((s) => ({
      ...s,
      courses: s.courses.map((c) => c.id === courseId ? { ...c, weekTarget: Math.max(0, Math.round(sec)) } : c),
    }));
  },
  setCourseRate(courseId, key, val) {
    window.PlannerStore.set((s) => ({
      ...s,
      courses: s.courses.map((c) => {
        if (c.id !== courseId) return c;
        const rates = { ...(c.ptsRates || window.DEFAULT_RATES), [key]: Math.max(0, Math.round(val)) };
        return { ...c, ptsRates: rates };
      }),
    }));
  },
  addNote(note) {
    const id = "n" + (Date.now() % 100000);
    const date = new Date().toLocaleDateString("ru-RU", { day: "2-digit", month: "2-digit" });
    window.PlannerStore.set((s) => ({
      ...s,
      notes: [{ id, pinned: false, hand: false, tags: [], links: [], date, ...note }, ...s.notes],
    }));
    window.pushToast({ id: ++toastSeq, text: "Заметка создана", pts: 0 });
  },
  updateNote(id, fields) {
    window.PlannerStore.set((s) => ({
      ...s,
      notes: s.notes.map((n) => (n.id === id ? { ...n, ...fields } : n)),
    }));
  },
  removeNote(id) {
    window.PlannerStore.set((s) => ({ ...s, notes: s.notes.filter((n) => n.id !== id) }));
    window.pushToast({ id: ++toastSeq, text: "Заметка удалена", pts: 0 });
  },
  toggleNotePin(id) {
    window.PlannerStore.set((s) => ({
      ...s,
      notes: s.notes.map((n) => (n.id === id ? { ...n, pinned: !n.pinned } : n)),
    }));
  },
  removeAchievement(id) {
    window.PlannerStore.set((s) => ({ ...s, achievements: (s.achievements || []).filter((a) => a.id !== id) }));
    window.pushToast({ id: ++toastSeq, text: "Достижение удалено", pts: 0 });
  },
  reset() {
    localStorage.removeItem("planer-state");
    window.authFetch("/api/state/reset", { method: "POST" }).catch(() => {});
    window.PlannerStore.set(JSON.parse(JSON.stringify(SEED)));
  },
};

/* ---- lookup helpers ------------------------------------------- */
window.PlannerLookup = {
  entity(state, id) {
    if (!id) return null;
    const m = state.materials.find((x) => x.id === id);
    if (m) return { ...m, _kind: "material" };
    const c = state.courses.find((x) => x.id === id);
    if (c) return { ...c, _kind: "course" };
    const n = state.notes.find((x) => x.id === id);
    if (n) return { ...n, _kind: "note" };
    return null;
  },
};
