/* Shared UI: icons, reveal, lightbox, cursor, sidebar, photo items */
const { useState, useEffect, useRef, useCallback, createContext, useContext } = React;

/* ---------------- Icons ---------------- */
const Icon = {
  tg: (p) => <svg viewBox="0 0 24 24" fill="currentColor" {...p}><path d="M21.9 4.3 18.7 19.4c-.2 1-.9 1.3-1.7.8l-4.7-3.5-2.3 2.2c-.3.3-.5.5-1 .5l.3-4.8 8.8-8c.4-.3-.1-.5-.6-.2L6.9 13.3l-4.7-1.5c-1-.3-1-1 .2-1.5l18.3-7c.9-.3 1.6.2 1.3 1.5z"/></svg>,
  vk: (p) => <svg viewBox="0 0 24 24" fill="currentColor" {...p}><path d="M13.2 17.6c-5.5 0-8.9-3.9-9-10.3h2.8c.1 4.7 2.2 6.7 3.8 7.1V7.3h2.6v4c1.6-.2 3.3-2 3.9-4h2.6c-.5 2.5-2.2 4.3-3.5 5 1.3.6 3.3 2.1 4 5h-2.9c-.6-1.8-2.1-3.3-4.1-3.5v3.5h-.2z"/></svg>,
  ig: (p) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" {...p}><rect x="3" y="3" width="18" height="18" rx="5"/><circle cx="12" cy="12" r="4"/><circle cx="17.5" cy="6.5" r="1" fill="currentColor" stroke="none"/></svg>,
  arrow: (p) => <svg viewBox="0 0 24 24" fill="none" strokeWidth="1.5" {...p}><path d="M5 12h14M13 6l6 6-6 6"/></svg>,
  close: (p) => <svg viewBox="0 0 24 24" fill="none" strokeWidth="1.5" {...p}><path d="M6 6l12 12M18 6 6 18"/></svg>,
  expand:(p) => <svg viewBox="0 0 24 24" fill="none" strokeWidth="1.5" {...p}><path d="M8 3H3v5M16 3h5v5M8 21H3v-5M16 21h5v-5"/></svg>,
};

/* ---------------- Lightbox context ---------------- */
const LBContext = createContext({ open: () => {} });
window.LBContext = LBContext;

function LightboxProvider({ children }) {
  const [items, setItems] = useState([]);
  const [idx, setIdx] = useState(-1);
  const open = useCallback((list, i) => { setItems(list); setIdx(i); }, []);
  const close = useCallback(() => setIdx(-1), []);
  const go = useCallback((d) => setIdx((p) => (p + d + items.length) % items.length), [items.length]);

  useEffect(() => {
    const onKey = (e) => {
      if (idx < 0) return;
      if (e.key === "Escape") close();
      if (e.key === "ArrowRight") go(1);
      if (e.key === "ArrowLeft") go(-1);
    };
    window.addEventListener("keydown", onKey);
    document.body.style.overflow = idx >= 0 ? "hidden" : "";
    return () => window.removeEventListener("keydown", onKey);
  }, [idx, go, close]);

  const cur = items[idx];
  return (
    <LBContext.Provider value={{ open }}>
      {children}
      <div className={"lightbox" + (idx >= 0 ? " open" : "")} onClick={close}>
        {cur && <>
          <div className="lb-count">{String(idx + 1).padStart(2, "0")} / {String(items.length).padStart(2, "0")}</div>
          <button className="lb-close" data-cursor onClick={close} aria-label="Закрыть">{Icon.close({})}</button>
          <button className="lb-btn lb-prev" data-cursor onClick={(e) => { e.stopPropagation(); go(-1); }}>{Icon.arrow({ style:{ transform:"rotate(180deg)", stroke:"#fff" } })}</button>
          <button className="lb-btn lb-next" data-cursor onClick={(e) => { e.stopPropagation(); go(1); }}>{Icon.arrow({ style:{ stroke:"#fff" } })}</button>
          <img className="lb-img" src={cur.src} alt={cur.cap || ""} onClick={(e) => e.stopPropagation()} key={cur.src} />
          {(cur.cap || cur.meta) && <div className="lb-cap">
            {cur.cap && <div className="t">{cur.cap}</div>}
            {cur.meta && <div className="m">{cur.meta}</div>}
          </div>}
        </>}
      </div>
    </LBContext.Provider>
  );
}

/* ---------------- Reveal ---------------- */
function useInView(opts) {
  const ref = useRef(null);
  const [seen, setSeen] = useState(false);
  useEffect(() => {
    const el = ref.current; if (!el) return;
    const io = new IntersectionObserver(([e]) => {
      if (e.isIntersecting) { setSeen(true); io.disconnect(); }
    }, { threshold: 0.12, ...opts });
    io.observe(el);
    return () => io.disconnect();
  }, []);
  return [ref, seen];
}
function Reveal({ children, className = "", as = "div", delay = 0, img = false, style = {} }) {
  const [ref, seen] = useInView();
  const As = as;
  return <As ref={ref} className={(img ? "reveal-img " : "reveal ") + className + (seen ? " in" : "")}
    style={{ transitionDelay: delay + "ms", ...style }}>{children}</As>;
}

/* ---------------- Photo item ---------------- */
function Photo({ item, list, index, num, className = "", aspect, style = {}, noInfo = false }) {
  const { open } = useContext(LBContext);
  return (
    <Reveal img className={"ph " + className} style={{ aspectRatio: aspect, ...style }}>
      <div data-cursor data-cursor-label="Смотреть" onClick={() => open(list, index)} style={{ width:"100%", height:"100%" }}>
        <img src={item.src} alt={item.cap || ""} loading="lazy" />
        {num != null && <span className="corner">{String(num).padStart(2, "0")}</span>}
        <span className="view-tag">{Icon.expand({})}</span>
        {!noInfo && (item.cap || item.meta) && <div className="ph-info">
          {item.cap && <span className="ph-cap">{item.cap}</span>}
          {item.meta && <span className="ph-meta">{item.meta}</span>}
        </div>}
      </div>
    </Reveal>
  );
}

/* role · city — the separating middot disappears when the line wraps */
function RoleCity() {
  const S = window.SITE;
  const ref = useRef(null);
  const [wrapped, setWrapped] = useState(false);
  useEffect(() => {
    const el = ref.current; if (!el) return;
    const segs = el.querySelectorAll(".rc-seg");
    const check = () => {
      if (segs.length < 2) return;
      setWrapped(Math.abs(segs[0].offsetTop - segs[1].offsetTop) > 1);
    };
    check();
    const ro = new ResizeObserver(check); ro.observe(el);
    if (document.fonts && document.fonts.ready) document.fonts.ready.then(check);
    return () => ro.disconnect();
  }, []);
  return (
    <span ref={ref} className={"rc" + (wrapped ? " wrapped" : "")}>
      <span className="rc-seg">{S.role}</span>
      <span className="rc-dot">·</span>
      <span className="rc-seg">{S.city}</span>
    </span>
  );
}

/* ---------------- Sidebar ---------------- */
function Sidebar({ route, navigate }) {
  const S = window.SITE;
  const portfolio = S.nav.filter((n) => n.group === "Портфолио");
  const flat = S.nav.filter((n) => !n.group);
  const [open, setOpen] = useState(false);

  useEffect(() => {
    document.body.style.overflow = open ? "hidden" : "";
    return () => { document.body.style.overflow = ""; };
  }, [open]);

  // close the drawer whenever the route changes
  useEffect(() => { setOpen(false); }, [route]);

  const go = (id) => { setOpen(false); navigate(id); };

  const link = (n, sub) => {
    const g = sub ? S.galleries[n.id] : null;
    return (
      <button key={n.id} className={"nav-link" + (sub ? " sub" : "") + (route === n.id ? " active" : "")}
        data-cursor style={sub && g ? { "--ln": g.accent } : undefined} onClick={() => go(n.id)}>
        <span className="dot" style={sub && g ? { background: g.accent } : undefined}></span>{n.label}
      </button>
    );
  };

  return (
    <>
      {/* mobile-only top bar */}
      <header className="topbar">
        <div className="brand" data-cursor onClick={() => go("home")}>
          <span className="brand-name">{S.name}</span>
        </div>
        <button className={"burger" + (open ? " open" : "")} data-cursor aria-label={open ? "Закрыть меню" : "Открыть меню"} aria-expanded={open} onClick={() => setOpen((o) => !o)}>
          <span></span><span></span><span></span>
        </button>
      </header>

      <div className={"nav-backdrop" + (open ? " show" : "")} onClick={() => setOpen(false)}></div>

      <aside className={"sidebar" + (open ? " open" : "")}>
        <div className="brand sidebar-brand" data-cursor onClick={() => go("home")}>
          <span className="brand-name">{S.name}</span>
          <span className="brand-role"><RoleCity /></span>
        </div>
        <nav className="nav">
          {flat.slice(0, 1).map((n) => link(n, false))}
          <div className="nav-group-label">Портфолио</div>
          {portfolio.map((n) => link(n, true))}
          <div style={{ height: 8 }}></div>
          {flat.slice(1).map((n) => link(n, false))}
        </nav>
        <div className="nav-spacer"></div>
        <div className="socials">
          <a href={S.telegram} target="_blank" rel="noreferrer" data-cursor aria-label="Telegram">{Icon.tg({})}</a>
          <a href={S.vk} target="_blank" rel="noreferrer" data-cursor aria-label="VK">{Icon.vk({})}</a>
          <a href={S.instagram} target="_blank" rel="noreferrer" data-cursor aria-label="Instagram">{Icon.ig({})}</a>
        </div>
        <div className="sidebar-foot">© {new Date().getFullYear()} · {S.city}</div>
      </aside>
    </>
  );
}

/* ---------------- Custom cursor ---------------- */
function Cursor() {
  const dot = useRef(null), ring = useRef(null), label = useRef(null), wrap = useRef(null);
  useEffect(() => {
    if (window.matchMedia("(pointer:coarse)").matches) return;
    let rx = 0, ry = 0, x = 0, y = 0, raf;
    const move = (e) => {
      x = e.clientX; y = e.clientY;
      if (dot.current) dot.current.style.transform = `translate(${x}px,${y}px) translate(-50%,-50%)`;
      if (label.current) label.current.style.transform = `translate(${x}px,${y + 4}px) translate(-50%,-50%)`;
      const t = e.target.closest("[data-cursor]");
      wrap.current.classList.toggle("hover", !!t);
      if (label.current) label.current.textContent = t && t.dataset.cursorLabel ? t.dataset.cursorLabel : "";
    };
    const loop = () => {
      rx += (x - rx) * 0.18; ry += (y - ry) * 0.18;
      if (ring.current) ring.current.style.transform = `translate(${rx}px,${ry}px) translate(-50%,-50%)`;
      raf = requestAnimationFrame(loop);
    };
    window.addEventListener("mousemove", move); loop();
    return () => { window.removeEventListener("mousemove", move); cancelAnimationFrame(raf); };
  }, []);
  return <div className="cursor" ref={wrap}>
    <div className="cursor-ring" ref={ring}></div>
    <div className="cursor-dot" ref={dot}></div>
    <div className="cursor-label" ref={label}></div>
  </div>;
}

Object.assign(window, { Icon, LightboxProvider, Reveal, useInView, Photo, RoleCity, Sidebar, Cursor });
