/* ============================================================
   ЗАГРУЗКА КОНТЕНТА ИЗ CMS (Sanity)
   Датасет читается публично, без токенов: в браузер уходит только
   id проекта. Опубликованные документы видны всем, черновики — нет,
   поэтому у редактора есть нормальный цикл «правлю → публикую».

   Как это устроено: массивы PHOTO/ART/NOTES/FRIENDS из data.jsx
   остаются на месте и служат запасным вариантом. После загрузки
   их содержимое ЗАМЕНЯЕТСЯ ВНУТРИ ТЕХ ЖЕ объектов (length = 0 + push),
   потому что остальные файлы держат ссылку именно на них.
   Если CMS недоступна — сайт просто показывает встроенные данные.
   ============================================================ */

const SANITY_PROJECT = "6bi84947";
const SANITY_DATASET = "production";

/* Режим просмотра черновика — открывается ссылкой /?preview=1
   Тогда контент берём не напрямую из Sanity, а через свою функцию
   /api/sanity: только у неё есть токен, которым видно неопубликованное.
   Обычный посетитель этого режима не видит и видеть не может. */
const PREVIEW = (function () {
  try { return /[?&]preview=1(&|$)/.test(location.search); } catch (e) { return false; }
})();

/* тексты сайта: значения по умолчанию = то, что было в вёрстке */
const SITE = {
  logoName: "Вера Маркелова",
  logoSub: "Фото · Арт",
  homeTitle1: "архив",
  homeTitle2: "света",
  homeLead: "Плёночная фотография и рисунок.\nВера Маркелова — Москва.\nСнято на 35 мм и средний формат, 2022—2025.",
  eyebrow: "Избранное",
  indexTitle: "Фотография",
  indexLink: "Все плёночные работы",
  photoLead: "Серия «У стены» — портреты у городских стен. Средний формат 6×6, дневной свет, минимум обработки.",
  artLead: "Этюды и работы на бумаге. Тушь, графит, акварель, уголь — ежедневная практика.",
  /* --- разделы --- */
  photoKicker: "Архив · Фотография",
  photoTitle: "Плёнка",
  artKicker: "Архив · Рисунок",
  artTitle: "Рисунок",
  indexTitleArt: "Рисунок",
  indexLinkArt: "Все арт-работы",
  friendsKicker: "Архив · Гости",
  friendsTitle: "Друзья",
  friendsLead: "Работы друзей-художников — приглашённый раздел рядом с «Рисунком». Выберите автора в дереве слева.",
  blogEyebrow: "Заметки",
  blogTitle: "Блог",
  blogLead: "Короткие записи о плёнке, печати и ежедневном рисунке. Без расписания — когда есть что сказать.",
  /* --- подвал --- */
  footerEyebrow: "Архив работ",
  footerMeta: "ФОТОГРАФИЯ · РИСУНОК\nПЛЁНКА · 35 ММ · 6×6\n© 2025",
  /* --- оформление --- */
  titleSize: "m",
  bgEnabled: false,
  bgSrc: "",
  bgSrcDark: "",
  bgMode: "sheet",
  bgOpacity: 12,
  bgInvert: true,
};

/* размер и формат картинки задаём в самом URL — CDN отдаёт webp/avif
   и не увеличивает изображение сверх оригинала (fit=max) */
function cdnImage(url, w) {
  if (!url) return "";
  return url + "?w=" + (w || 1200) + "&q=80&auto=format&fit=max";
}

const WORK_FIELDS =
  '"id": _id, title, meta, ratio, label, desc, tone, ' +
  '"src": image.asset->url, "hot": image.hotspot{x, y}, ' +
  /* имя автора: пусто — работа хозяйки сайта, иначе подпись под работой */
  '"author": artist->name';

const GROQ = `{
  "site": *[_id=="siteText"][0]{
    logoName, logoSub, footerEyebrow, footerMeta,
    homeTitle1, homeTitle2, titleSize, homeLead, eyebrow,
    indexTitle, indexLink, indexTitleArt, indexLinkArt,
    photoKicker, photoTitle, photoLead,
    artKicker, artTitle, artLead,
    friendsKicker, friendsTitle, friendsLead,
    blogEyebrow, blogTitle, blogLead,
    bgEnabled, bgMode, bgOpacity, bgInvert,
    "bgSrc": bgImage.asset->url, "bgSrcDark": bgImageDark.asset->url
  },
  "photo": *[_type=="work" && kind=="photo"] | order(order asc){${WORK_FIELDS}},
  "art":   *[_type=="work" && kind=="art"]   | order(order asc){${WORK_FIELDS}},
  "notes": *[_type=="note"] | order(date desc){"id": _id, title, tag, excerpt, body, date},
  "artists": *[_type=="artist"] | order(order asc){
    "id": _id, name, role, note,
    "works": *[_type=="work" && kind=="friend" && artist._ref==^._id] | order(order asc){${WORK_FIELDS}}
  }
}`;

/* ISO-дата -> 12.03.2024 */
function ruDate(iso) {
  const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(String(iso || ""));
  return m ? m[3] + "." + m[2] + "." + m[1] : "";
}

function normWork(w) {
  return {
    id: w.id,
    title: w.title || "",
    meta: w.meta || "",
    ratio: w.ratio || "1:1",
    label: w.label || "",
    desc: w.desc || "",
    tone: typeof w.tone === "number" ? w.tone : 0.5,
    /* точка фокуса из Studio -> позиция кадрирования на сайте.
       Так автор сам решает, что не обрезать, когда пропорции не совпадают. */
    pos: w.hot && typeof w.hot.x === "number"
      ? Math.round(w.hot.x * 100) + "% " + Math.round(w.hot.y * 100) + "%"
      : "center",
    /* пустая строка, а не undefined: в запасных данных data.jsx автора нет вообще */
    author: w.author || "",
    src: cdnImage(w.src),
  };
}

/* замена содержимого массива, не теряя саму ссылку на него */
function refill(target, items) {
  if (!Array.isArray(target) || !Array.isArray(items) || !items.length) return;
  target.length = 0;
  items.forEach((it) => target.push(it));
}

/* Состояние загрузки — объект, чтобы его можно было менять на месте
   (см. комментарий в .catch ниже). */
const CMS = { failed: false };

/* Берём api, а не apicdn: кэш CDN отдаёт старое содержимое ещё долго после
   правки, и редактор решил бы, что сайт сломался. Трафик тут небольшой,
   так что свежесть важнее кэша. */
function loadContent() {
  const url = PREVIEW
    ? "/api/sanity?drafts=1&query=" + encodeURIComponent(GROQ)
    : "https://" + SANITY_PROJECT + ".api.sanity.io/v2024-01-01/data/query/" +
      SANITY_DATASET + "?query=" + encodeURIComponent(GROQ);

  return fetch(url)
    .then((r) => { if (!r.ok) throw new Error("CMS: HTTP " + r.status); return r.json(); })
    .then((json) => {
      const d = json.result || {};

      /* Пустое текстовое поле в CMS не должно стирать встроенный текст,
         поэтому строки берём только непустые. А вот числа и галочки наоборот:
         прозрачность 0 и выключенная галочка — это выбор редактора, а не «пусто». */
      if (d.site) Object.keys(d.site).forEach((k) => {
        const v = d.site[k];
        if (typeof v === "number" || typeof v === "boolean") SITE[k] = v;
        else if (v) SITE[k] = v;
      });

      refill(PHOTO, (d.photo || []).map(normWork));
      refill(ART, (d.art || []).map(normWork));

      refill(NOTES, (d.notes || []).map((n) => ({
        id: n.id,
        date: ruDate(n.date),
        tag: n.tag || "",
        title: n.title || "",
        excerpt: n.excerpt || "",
        body: String(n.body || "").split(/\n\s*\n/).filter(Boolean),
      })));

      refill(FRIENDS, (d.artists || []).map((a) => ({
        id: a.id,
        name: a.name || "",
        role: a.role || "",
        note: a.note || "",
        works: (a.works || []).map(normWork),
      })));

      return true;
    })
    .catch((e) => {
      /* Сайт продолжает работать на встроенных данных — но молчать об этом
         нельзя: в data.jsx лежит демонстрационный набор, и редактор видит
         чужие работы вместо своих, решая, что правка не сохранилась.
         Флаг держим полем объекта, а не отдельной переменной: скрипты делят
         значения лексически, и переприсваивание сюда бы не дошло. */
      CMS.failed = true;
      if (window.console) console.warn("Контент из CMS не загружен:", e.message);
      return false;
    });
}

Object.assign(window, { SITE, CMS, loadContent, cdnImage, PREVIEW });
