const { useState, useEffect, useMemo, useRef } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accent": "#FFD53E",
  "borderColor": "#EFEFEF",
  "showAvailability": true,
  "cardRadius": 6
} /*EDITMODE-END*/;

// ---------- Site config (loaded from site.config.json) ----------
const SITE_CONFIG_DEFAULTS = {
  contact: {
    email: "batuich@gmail.com",
    telegram: "https://t.me/batuich"
  },
  social: {
    linkedin: "https://www.linkedin.com/in/oleg-tcybikov-1742a273/",
    x: "https://x.com/batuich",
    instagram: "https://www.instagram.com/madebybatuich/",
    dribbble: "https://dribbble.com/batuich",
    behance: "https://www.behance.net/batuich"
  },
  features: { showAvailability: true }
};
const SiteConfigContext = React.createContext(SITE_CONFIG_DEFAULTS);
const useSiteConfig = () => React.useContext(SiteConfigContext);

// ---------- Tooltip ----------
function Tooltip({ children, label, side = "bottom" }) {
  const [open, setOpen] = useState(false);
  const timer = useRef(null);
  const show = () => {
    clearTimeout(timer.current);
    timer.current = setTimeout(() => setOpen(true), 120);
  };
  const hide = () => {
    clearTimeout(timer.current);
    setOpen(false);
  };
  return (
    <span
      className="relative inline-flex"
      onMouseEnter={show}
      onMouseLeave={hide}
      onFocus={show}
      onBlur={hide}>
      
      {children}
      <span
        role="tooltip"
        className={`pointer-events-none absolute ${side === "top" ? "bottom-full mb-2" : "top-full mt-2"} left-1/2 -translate-x-1/2 whitespace-nowrap font-mono text-[10px] uppercase tracking-wider px-2 py-1 bg-[#111] text-white rounded-[3px] transition-opacity duration-150 ${open ? "opacity-100" : "opacity-0"}`}>
        
        {label}
      </span>
    </span>);

}

// ---------- Buttons ----------
function PrimaryButton({ href, onClick, children, type = "a" }) {
  const cls =
  "inline-flex items-center justify-center h-10 px-4 bg-[#111] text-white text-[13px] font-medium rounded-[4px] transition-colors duration-150 hover:bg-black active:bg-[#000]";
  if (type === "button")
  return (
    <button onClick={onClick} className={cls}>
        {children}
      </button>);

  return (
    <a href={href} className={cls}>
      {children}
    </a>);

}

function SecondaryButton({ href, onClick, children, target, rel, type = "a" }) {
  const cls =
  "inline-flex items-center justify-center h-10 px-4 bg-white text-[#111] text-[13px] font-medium border border-[#EFEFEF] rounded-[4px] transition-colors duration-150 hover:border-[#111]";
  if (type === "button")
  return (
    <button onClick={onClick} className={cls}>
        {children}
      </button>);

  return (
    <a href={href} target={target} rel={rel} className={cls}>
      {children}
    </a>);

}

// ---------- Contact actions ----------
function ContactActions({ accent, compact = false }) {
  const config = useSiteConfig();
  const EMAIL = config.contact.email;
  const TG_URL = config.contact.telegram;
  const [copied, setCopied] = useState(false);
  const [hovered, setHovered] = useState(false);
  const copy = async () => {
    try {
      await navigator.clipboard.writeText(EMAIL);
    } catch (e) {
      // fallback
      const ta = document.createElement("textarea");
      ta.value = EMAIL;
      document.body.appendChild(ta);
      ta.select();
      try {document.execCommand("copy");} catch {}
      ta.remove();
    }
    setCopied(true);
    setTimeout(() => setCopied(false), 1800);
  };

  return (
    <div className="flex flex-wrap gap-2 items-center" style={{ gap: "16px", padding: "0px 0px 0px 4px" }}>
      <span
        className="relative inline-flex"
        onMouseEnter={() => setHovered(true)}
        onMouseLeave={() => setHovered(false)}>
        
        <button
          type="button"
          onClick={copy}
          className="inline-flex items-center justify-center h-10 px-4 text-[13px] font-medium rounded-[4px] border transition-colors duration-150 cursor-pointer whitespace-nowrap"
          style={{ background: accent, color: "#111", borderColor: accent }}
          onMouseEnter={(e) => e.currentTarget.style.borderColor = "#111"}
          onMouseLeave={(e) => e.currentTarget.style.borderColor = accent}>
          Let's talk:&nbsp;{EMAIL}
        </button>

        {/* Hover/click popup — same placement as Telegram tooltip */}
        <span
          role="status"
          aria-live="polite"
          className={`pointer-events-none absolute top-full mt-2 left-1/2 -translate-x-1/2 whitespace-nowrap font-mono text-[10px] uppercase tracking-wider px-2 py-1 bg-[#111] text-white rounded-[3px] transition-opacity duration-150 ${copied || hovered ? "opacity-100" : "opacity-0"}`}>
          {copied ? "Email copied to clipboard" : "Copy email"}
        </span>
      </span>

      <Tooltip label="Message me on Telegram">
        <a
          href={TG_URL}
          target="_blank"
          rel="noreferrer"
          className="inline-flex items-center justify-center h-10 px-4 bg-white text-[#111] text-[13px] font-medium border border-[#EFEFEF] rounded-[4px] transition-colors duration-150 hover:!border-[#111] gap-1.5" style={{ borderColor: "rgb(255, 213, 62)" }}
          onMouseEnter={(e) => e.currentTarget.style.borderColor = "#111"}
          onMouseLeave={(e) => e.currentTarget.style.borderColor = "rgb(255, 213, 62)"}>
          
          Telegram
          <TelegramIcon />
        </a>
      </Tooltip>
    </div>);

}

// ---------- Bouncing badge (DVD-screensaver style) ----------
function BouncingBadge({ containerRef }) {
  const config = useSiteConfig();
  const EMAIL = config.contact.email;
  const elRef = useRef(null);
  // Use refs (not state) for mutable physics so rAF doesn't trigger re-renders.
  const posRef = useRef({ x: 24, y: 24 });
  const velRef = useRef({ vx: 0, vy: 0 });
  const rotRef = useRef({ angle: 0, omega: 0 }); // degrees, deg/sec

  useEffect(() => {
    const container = containerRef.current;
    const el = elRef.current;
    if (!container || !el) return;

    const SPEED = 100; // px / second — constant
    const ROT_SPEED = 90; // deg / second — constant magnitude
    // initial direction: 45° upward (right + up)
    const angle = -Math.PI / 4;
    velRef.current = {
      vx: Math.cos(angle) * SPEED,
      vy: Math.sin(angle) * SPEED
    };
    // initial rotation: clockwise since dx > 0 (matches "next top/bottom hit" rule preemptively)
    rotRef.current = { angle: 0, omega: ROT_SPEED };

    // Start position: somewhere inside the container
    const cw0 = container.clientWidth;
    const ch0 = container.clientHeight;
    const bw0 = el.offsetWidth;
    const bh0 = el.offsetHeight;
    posRef.current = {
      x: Math.max(0, Math.min(cw0 - bw0, 700)),
      y: Math.max(0, Math.min(ch0 - bh0, 200))
    };
    el.style.transform = `translate3d(${posRef.current.x}px, ${posRef.current.y}px, 0) rotate(0deg)`;

    let rafId;
    let last = performance.now();

    const tick = (now) => {
      const dt = Math.min(0.05, (now - last) / 1000); // clamp to avoid jumps on tab focus
      last = now;

      const cw = container.clientWidth;
      const ch = container.clientHeight;
      const bw = el.offsetWidth;
      const bh = el.offsetHeight;

      let { x, y } = posRef.current;
      let { vx, vy } = velRef.current;
      let { angle: rotAngle, omega } = rotRef.current;

      x += vx * dt;
      y += vy * dt;

      // Horizontal edges — keep current rotation direction (do not change it)
      if (x <= 0) {x = 0;vx = Math.abs(vx);} else
      if (x + bw >= cw) {x = cw - bw;vx = -Math.abs(vx);}

      // Vertical edges — set rotation direction from current dx
      if (y <= 0) {
        y = 0;
        vy = Math.abs(vy);
        omega = vx >= 0 ? ROT_SPEED : -ROT_SPEED; // CW if moving right, CCW if left
      } else if (y + bh >= ch) {
        y = ch - bh;
        vy = -Math.abs(vy);
        omega = vx >= 0 ? ROT_SPEED : -ROT_SPEED;
      }

      // Smooth continuous rotation
      rotAngle += omega * dt;

      posRef.current = { x, y };
      velRef.current = { vx, vy };
      rotRef.current = { angle: rotAngle, omega };

      el.style.transform = `translate3d(${x}px, ${y}px, 0) rotate(${rotAngle}deg)`;
      rafId = requestAnimationFrame(tick);
    };

    rafId = requestAnimationFrame(tick);

    // On container resize, re-clamp position so badge stays inside
    const ro = new ResizeObserver(() => {
      const cw = container.clientWidth;
      const ch = container.clientHeight;
      const bw = el.offsetWidth;
      const bh = el.offsetHeight;
      posRef.current.x = Math.max(0, Math.min(cw - bw, posRef.current.x));
      posRef.current.y = Math.max(0, Math.min(ch - bh, posRef.current.y));
    });
    ro.observe(container);

    return () => {
      cancelAnimationFrame(rafId);
      ro.disconnect();
    };
  }, [containerRef]);

  return (
    <a
      ref={elRef}
      href={`mailto:${EMAIL}?subject=Project%20inquiry`}
      aria-label="Available for new projects"
      className="hidden md:flex absolute top-0 left-0 w-[120px] h-[120px] items-center justify-center z-10 will-change-transform">
      
      <img
        src="assets/availability-badge.svg"
        alt="Available for new projects"
        className="w-full h-full select-none pointer-events-none"
        draggable="false" />
      
    </a>);

}

// ---------- Hero ----------
function Hero({ accent, showAvailability }) {
  const heroRef = useRef(null);
  return (
    <section ref={heroRef} className="relative py-10 md:py-[60px] border-b border-[#EFEFEF] overflow-hidden bg-white isolate">
      {showAvailability && <BouncingBadge containerRef={heroRef} />}
      <div className="max-w-[1280px] mx-auto px-6 md:px-10 relative">
        <div className="flex flex-col gap-10 relative lg:max-w-[78%]">
            <div style={{ fontSize: "9px", width: "100%", maxWidth: "1000px", height: "auto" }}>
              <div className="flex items-center gap-3 mb-4" style={{ margin: "0px 0px 32px", padding: "0px 0px 0px 4px" }}>
                <img
                src="uploads/x-ln-ava-02.jpg"
                alt="Oleg Tcybikov"
                className="w-9 h-9 rounded-full object-cover border border-[#EFEFEF]" />

                <div className="font-mono text-[11px] uppercase tracking-wider text-[#6B6B6B]">OLEG TCYBIKOV, A MULTIDISCIPLINARY DESIGNER

              </div>
              </div>
              {/* Inversion/blend treatment — source colors are light so that
                 mix-blend-mode: difference renders them DARK over the white page
                 (normal, readable) and LIGHT over the dark sphere image cards.
                 - Headline source = pure white  -> black on white, white over dark.
                 - Subheadline source = warm desaturated gray -> reads as neutral
                   gray on white, shifts toward warm/yellowish light over dark.
                 Applied to the <h1> wrapper ONLY; siblings (avatar, eyebrow,
                 buttons, badge, nav) are untouched. No isolation/opacity/filter
                 sits between this and the sphere, so the blend reaches it. */}
              <h1 className="hero-headline text-[40px] md:text-[64px] leading-[0.98] tracking-[-0.025em] font-medium text-pretty max-w-[18ch]" style={{ lineHeight: "1", padding: "0px", color: "#FFFFFF", mixBlendMode: "difference", position: "relative" }}>
                Identity, decks, websites, UI/UX, and more —<br /><span className="hero-subhead" style={{ color: "#6B6553" }}>full design support for startups & growing teams.</span>
              </h1>
            </div>

            <ContactActions accent={accent} />
          </div>
      </div>
      <div id="hero-sphere" aria-hidden="true"></div>
    </section>);
}

// ---------- Filters ----------
function Filters({ tags, active, counts, totalCount, onToggle, onClear }) {
  const allActive = !active;
  return (
    <div className="flex flex-wrap items-center gap-1.5" style={{ padding: "0px 0px 0px 2px" }}>
      <button
        onClick={onClear}
        className={`inline-flex items-center gap-1.5 h-8 px-3 rounded-[3px] border text-[12px] font-medium transition-colors duration-150 ${
        allActive ?
        "bg-[#111] text-white border-[#111]" :
        "bg-white text-[#111] border-[#EFEFEF] hover:border-[#111]"}`
        }>
        <span>All</span>
        <span
          className={`font-mono text-[10px] ${allActive ? "text-white/60" : "text-[#9A9A9A]"}`}>
          {totalCount}
        </span>
      </button>
      {tags.map((tag) => {
        const isActive = active === tag;
        return (
          <button
            key={tag}
            onClick={() => onToggle(tag)}
            className={`inline-flex items-center gap-1.5 h-8 px-3 rounded-[3px] border text-[12px] font-medium transition-colors duration-150 ${
            isActive ?
            "bg-[#111] text-white border-[#111]" :
            "bg-white text-[#111] border-[#EFEFEF] hover:border-[#111]"}`
            }>
            
            <span>{tag}</span>
            <span
              className={`font-mono text-[10px] ${isActive ? "text-white/60" : "text-[#9A9A9A]"}`}>
              
              {counts[tag] || 0}
            </span>
          </button>);

      })}
    </div>);

}

// ---------- Works section ----------
function Works() {
  const [active, setActive] = useState(null);
  const [works, setWorks] = useState([]);
  const [loadError, setLoadError] = useState(null);

  useEffect(() => {
    let cancelled = false;
    window.loadWorks().
    then((data) => {if (!cancelled) setWorks(data);}).
    catch((err) => {if (!cancelled) setLoadError(err.message || String(err));});
    return () => {cancelled = true;};
  }, []);

  // Build the tag list dynamically from cases.json — every unique tag that
  // appears on at least one case becomes a filter, with its true count.
  const counts = useMemo(() => {
    const c = {};
    works.forEach((w) => (w.tags || []).forEach((t) => {
      c[t] = (c[t] || 0) + 1;
    }));
    return c;
  }, [works]);

  const filtered = useMemo(() => {
    const list = active ? works.filter((w) => w.tags.includes(active)) : works;
    return [...list].sort((a, b) => (b.order || 0) - (a.order || 0));
  }, [active, works]);

  // Sort by count desc, then alpha for stable order on ties.
  const sortedTags = useMemo(() => {
    return Object.keys(counts).sort((a, b) => {
      const diff = (counts[b] || 0) - (counts[a] || 0);
      return diff !== 0 ? diff : a.localeCompare(b);
    });
  }, [counts]);

  // If the active tag disappears from the data (e.g. cases.json edited),
  // gracefully fall back to "All".
  useEffect(() => {
    if (active && !counts[active]) setActive(null);
  }, [active, counts]);

  const toggle = (tag) => setActive((cur) => cur === tag ? null : tag);

  return (
    <section className="py-16 md:py-24 border-b border-[#EFEFEF]" style={{ padding: "40px 0px 64px" }}>
      <div className="max-w-[1280px] mx-auto px-6 md:px-10">
        <div className="flex flex-col gap-6 mb-10" style={{ margin: "0px 0px 30px" }}>
          <div className="flex items-end justify-between gap-6 flex-wrap">
            <div>
              <h2 className="text-[28px] md:text-[36px] tracking-[-0.02em] font-medium text-[#111]">Selected tasks

              </h2>
            </div>
          </div>

          <Filters
            tags={sortedTags}
            active={active}
            counts={counts}
            totalCount={works.length}
            onToggle={toggle}
            onClear={() => setActive(null)} />
          
        </div>

        {loadError &&
        <div className="font-mono text-[11px] uppercase tracking-wider text-[#B00] mb-4">
            Failed to load cases.json — {loadError}
          </div>
        }

        <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 md:gap-5" style={{ padding: "0px 0px 0px 2px" }}>
          {filtered.map((w) =>
          <div
            key={w.id}
            className="opacity-0 animate-[fadeIn_240ms_ease-out_forwards]"
            style={{ animationDelay: `${filtered.indexOf(w) % 8 * 25}ms` }}>
            
              <WorkCard work={w} />
            </div>
          )}
          <div
            key="__end-card"
            className="opacity-0 animate-[fadeIn_240ms_ease-out_forwards]"
            style={{ animationDelay: `${filtered.length % 8 * 25}ms` }}>
            <EndCard />
          </div>
        </div>

        {filtered.length === 0 &&
        <div className="py-20 text-center text-[#6B6B6B] text-[14px]">
            No work matches that filter yet. <button className="underline" onClick={() => setActive(null)}>Clear filter</button>
          </div>
        }
      </div>
    </section>);

}

// ---------- Footer ----------
function Footer({ accent }) {
  const config = useSiteConfig();
  const year = new Date().getFullYear();
  return (
    <footer className="py-16 md:py-24" style={{ padding: "64px 0px 96px" }}>
      <div className="max-w-[1280px] mx-auto px-6 md:px-10">
        <div className="grid grid-cols-12 gap-x-6 gap-y-10">
          <div className="col-span-12 md:col-span-7">
            <h2 className="text-[32px] md:text-[48px] leading-[1] tracking-[-0.02em] font-medium text-[#111] mb-8 text-pretty">
              Have something to launch? <span style={{ color: "#9A9A9A" }}>Let's make it sharp.</span>
            </h2>
            <ContactActions accent={accent} />

            <div className="mt-8" style={{ padding: "0px 0px 0px 4px" }}>
              <div className="flex flex-wrap items-center gap-x-2 gap-y-1 text-[13px] text-[#6B6B6B]">
                <span>Also on:</span>
                {[
                { label: "LinkedIn", href: config.social.linkedin },
                { label: "X", href: config.social.x },
                { label: "Instagram", href: config.social.instagram },
                { label: "Dribbble", href: config.social.dribbble },
                { label: "Behance", href: config.social.behance }].
                filter((l) => l.href).
                map((link, i, arr) =>
                <React.Fragment key={link.label}>
                    <a
                    href={link.href}
                    target="_blank"
                    rel="noopener noreferrer"
                    className="transition-colors hover:text-[#111] hover:underline underline-offset-4 decoration-[#9A9A9A]">

                      {link.label}
                    </a>
                    {i < arr.length - 1 && <span className="text-[#C9C9C9]" aria-hidden="true">·</span>}
                  </React.Fragment>
                )}
              </div>
            </div>
          </div>

          <div className="col-span-12 md:col-span-5 md:pl-10 md:border-l md:border-[#EFEFEF] flex flex-col gap-5">
            {[
            { label: "Company name", value: "Batuich Design Studio DOO" },
            { label: "Legal form", value: "Društvo sa ograničenom odgovornošću (DOO)" },
            { label: "Founded", value: "2024" },
            { label: "Registration", value: "Budva, Montenegro" },
            { label: "Registration number", value: "278455" },
            { label: "Tax number (VAT)", value: "03716805" }].
            map((row) =>
            <div key={row.label} className="flex flex-col gap-1">
                <div className="font-mono text-[10px] uppercase tracking-wider text-[#6B6B6B]">
                  {row.label}
                </div>
                <div className="text-[13px] text-[#111] leading-snug">{row.value}</div>
              </div>
            )}
          </div>
        </div>

        <div className="mt-16 pt-6 border-t border-[#EFEFEF] flex items-center justify-between flex-wrap gap-4">
          <div className="font-mono text-[10px] uppercase tracking-wider text-[#6B6B6B]">
            © {year} Batuich Design Studio DOO · All rights reserved
          </div>
          <div className="font-mono text-[10px] uppercase tracking-wider text-[#6B6B6B]">V1.1 — LAST UPDATED MAY 2026

          </div>
        </div>
      </div>
    </footer>);

}

// ---------- Header ----------
function Header() {
  return (
    <header className="border-b border-[#EFEFEF]">
      <div className="max-w-[1280px] mx-auto px-6 md:px-10 h-14 flex items-center justify-between">
        <div className="flex items-center gap-2">
          <span className="w-2 h-2 bg-[#111] rounded-[1px]" aria-hidden="true" />
          <span className="font-mono text-[12px] uppercase tracking-wider text-[#111]">Batuich</span>
          <span className="font-mono text-[11px] text-[#9A9A9A] hidden sm:inline">— Design Studio</span>
        </div>
        <nav className="flex items-center gap-5 font-mono text-[11px] uppercase tracking-wider text-[#6B6B6B]">
          <a href="#works" className="hover:text-[#111]">Works</a>
          <a href="#contact" className="hover:text-[#111]">Contact</a>
        </nav>
      </div>
    </header>);

}

// ---------- App ----------
function App() {
  const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [siteConfig, setSiteConfig] = useState(SITE_CONFIG_DEFAULTS);

  // Load site.config.json (email, social links, feature flags)
  useEffect(() => {
    let cancelled = false;
    fetch("site.config.json", { cache: "no-cache" }).
    then((r) => r.ok ? r.json() : null).
    then((cfg) => {
      if (cancelled || !cfg) return;
      setSiteConfig({
        contact: { ...SITE_CONFIG_DEFAULTS.contact, ...(cfg.contact || {}) },
        social: { ...SITE_CONFIG_DEFAULTS.social, ...(cfg.social || {}) },
        features: { ...SITE_CONFIG_DEFAULTS.features, ...(cfg.features || {}) }
      });
    }).
    catch(() => {/* keep defaults */});
    return () => {cancelled = true;};
  }, []);

  // Live-apply tweakable tokens via CSS variables
  useEffect(() => {
    const r = document.documentElement;
    r.style.setProperty("--accent", tweaks.accent);
    r.style.setProperty("--border", tweaks.borderColor);
    r.style.setProperty("--radius", `${tweaks.cardRadius}px`);
  }, [tweaks]);

  const showAvailability = tweaks.showAvailability && siteConfig.features.showAvailability !== false;

  return (
    <SiteConfigContext.Provider value={siteConfig}>
    <div className="min-h-screen bg-white text-[#111]">
      <main>
        <Hero accent={tweaks.accent} showAvailability={showAvailability} />
        <div id="works"><Works /></div>
        <div id="contact"><Footer accent={tweaks.accent} /></div>
      </main>

      <TweaksPanel title="Tweaks">
        <TweakSection title="Accent">
          <TweakColor label="Accent color" value={tweaks.accent} onChange={(v) => setTweak("accent", v)} />
          <TweakRadio
              label="Preset"
              value={tweaks.accent}
              onChange={(v) => setTweak("accent", v)}
              options={[
              { label: "Yellow", value: "#FFD53E" },
              { label: "Lime", value: "#D4FF3E" },
              { label: "Coral", value: "#FF6F3E" },
              { label: "Sky", value: "#3EB8FF" }]
              } />
          
        </TweakSection>
        <TweakSection title="Layout">
          <TweakSlider label="Card radius" value={tweaks.cardRadius} min={0} max={16} step={1} onChange={(v) => setTweak("cardRadius", v)} />
          <TweakColor label="Border color" value={tweaks.borderColor} onChange={(v) => setTweak("borderColor", v)} />
          <TweakToggle label="Show availability badge" value={tweaks.showAvailability} onChange={(v) => setTweak("showAvailability", v)} />
        </TweakSection>
      </TweaksPanel>
    </div>
    </SiteConfigContext.Provider>);

}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);