/* =============================================================
   Desk mode — the small part that is always loaded: the switch in
   the header, the helpers the embedded site needs, the shared texts
   and LazyView. The 3D office (desk-mode.jsx and the retro computer)
   is fetched and compiled only when the switch is clicked, and the
   street and its buildings only when you go there (lazy-load.js).
   LazyView plays the transition into each mode while it loads.
   ============================================================= */

const DESK_TEXT = {
  backSite: "Tilbake til solidlab.no",
  toggle: "Skrivebordsmodus",
  enter: "Solidlab home office – please select screen",
  enterSite: "Bruk solidlab.no",
  enterRetro: "Bruk datamaskinen fra 2004",
  back: "Tilbake til skrivebordet",
  close: "Lukk skrivebordsmodus",
  soundOff: "Skru av lyd",
  soundOn: "Skru på lyd",
  backShort: "Tilbake",
  loading: "Rydder skrivebordet …",
  street: "Virtuell verden",
  streetLoading: "Finner et ledig bord …",
  /* The transitions into the 3D modes (LazyView) */
  portal: {
    desk: { kicker: "Solidlab home office", steps: ["Henter kontoret …", "Rydder skrivebordet …"] },
    street: { kicker: "Solidlab virtual world", place: "Stavanger", steps: ["Henter byen …", "Finner et ledig bord …"] },
    failed: "Fikk ikke lastet – prøv igjen",
  },
  burp: "BØØØRP",
};

/* True inside the iframe, so the switch never nests a desk inside a desk */
const IS_DESK_EMBED = (() => {
  try { return window.self !== window.top || /[?&]embed=1/.test(window.location.search); }
  catch (e) { return true; }
})();

/* Inside the embedded site, other domains (ki.solidlab.no, support, social media) refuse to load
   in a frame and the screen just goes white. Open those in a new tab instead. */
const deskOpenExternal = (href) => {
  try {
    const u = new URL(href, window.location.href);
    if (IS_DESK_EMBED && /^https?:$/.test(u.protocol) && u.origin !== window.location.origin) {
      window.open(u.href, "_blank", "noopener");
      return true;
    }
  } catch (e) { /* not a URL */ }
  return false;
};
window.slNavigate = (href) => { if (!deskOpenExternal(href)) window.location.href = href; };
if (IS_DESK_EMBED) {
  document.addEventListener("click", (e) => {
    const a = e.target.closest && e.target.closest("a[href]");
    if (!a || e.defaultPrevented) return;
    if (deskOpenExternal(a.getAttribute("href"))) e.preventDefault();
  }, true);
}

/* Inside the embedded site: pass Esc up to the desk, since key events stop at the iframe */
if (IS_DESK_EMBED) {
  try {
    document.addEventListener("keydown", (e) => {
      if (e.key === "Escape" && window.parent !== window) window.parent.postMessage({ type: "sl-desk-esc" }, window.location.origin);
    });
  } catch (e) { /* not framed */ }
}

function deskSupported() {
  if (IS_DESK_EMBED) return false;
  if (window.innerWidth < 1024) return false;
  try { const c = document.createElement("canvas"); return !!(c.getContext("webgl2") || c.getContext("webgl")); }
  catch (e) { return false; }
}

/* Files behind the switch, in the order they must run */
const DESK_FILES = ["retro-life.jsx", "retro-word.jsx", "retro-os.jsx", "desk-mode.jsx"];

/* Transitions into the 3D modes, in the site's own look (styles under .sl-portal in no.css).
   desk:   the page shrinks into a monitor on a dark background, so the site becomes the screen on the desk.
   street: a skyline rises against the sky you see through the office window.
   They run while the code is fetched and compiled and the 3D scene is built (the view is mounted
   hidden underneath), then fade out over the scene once it reports ready (readySelector).
   Never shorter than PORTAL_MIN_MS, so a quick load still looks intended. Esc closes it again. */
const PORTAL_MIN_MS = 700;
const PORTAL_SKYLINE = [   /* left %, width %, height % of the city band, facade colour (street palette) */
  [-2, 10, 52, "#62717C"], [7, 8, 70, "#D7B47A"], [14, 11, 44, "#B4553D"], [24, 7, 86, "#7E6A55"], [30, 12, 58, "#8C9A86"],
  [41, 9, 100, "#15191B"], [49, 11, 64, "#A8462F"], [59, 8, 80, "#62717C"], [66, 12, 48, "#D7B47A"], [77, 9, 72, "#7E6A55"],
  [85, 10, 56, "#B4553D"], [94, 9, 66, "#8C9A86"],
];

/* Dark outside in Stavanger? The street follows the real time of day, so the skyline does too.
   Rough sunrise/sunset hour per month is close enough for a backdrop. */
const PORTAL_SUN = { rise: [9, 8, 7, 6, 5, 4, 4, 5, 7, 8, 8, 9], set: [16, 17, 18, 20, 21, 22, 22, 21, 20, 18, 16, 15] };
function portalIsNight() {
  try {
    const p = Object.fromEntries(new Intl.DateTimeFormat("en-GB", { timeZone: "Europe/Oslo", month: "numeric", hour: "numeric", hour12: false })
      .formatToParts(new Date()).map(x => [x.type, x.value]));
    const m = +p.month - 1, h = +p.hour % 24;
    return h < PORTAL_SUN.rise[m] || h >= PORTAL_SUN.set[m];
  } catch (e) { return false; }
}

function PortalSkyline() {
  const [night] = useState(portalIsNight);
  return (
    <div className={"sl-portal__sky" + (night ? " is-night" : "")} aria-hidden="true">
      <span className="sl-portal__sun"></span>
      <div className="sl-portal__city">
        {PORTAL_SKYLINE.map(([x, w, h, c], i) => (
          <span key={i} style={{ left: x + "%", width: w + "%", height: h + "%", backgroundColor: c, animationDelay: 120 + i * 45 + "ms" }}></span>
        ))}
      </div>
      <div className="sl-portal__ground"></div>
    </div>
  );
}

function LazyView({ files, isLoaded, variant, readySelector, onClose, children }) {
  const T = DESK_TEXT.portal[variant];
  const [phase, setPhase] = useState(isLoaded ? "build" : "load");   /* load -> build -> reveal -> done */
  const [failed, setFailed] = useState(false);
  const hostRef = useRef(null);
  const t0 = useRef(performance.now());
  const closeRef = useRef(onClose); closeRef.current = onClose;

  /* The page grows back to full size; the clip stays on until it has, so nothing outside the view flashes in */
  const unshrink = () => {
    const html = document.documentElement;
    if (!html.classList.contains("sl-portal-desk")) return;
    html.classList.remove("sl-portal-desk"); html.classList.add("sl-portal-clip");
    setTimeout(() => { if (!html.classList.contains("sl-portal-desk")) html.classList.remove("sl-portal-clip"); }, 760);
  };
  /* Desk: shrink the page into a screen. Only what is in view is kept (clip-path), scaled around the middle
     of the viewport. Undone once the desk covers the page, or right away if the visitor backs out. */
  useEffect(() => {
    if (variant !== "desk") return;
    const html = document.documentElement, root = document.getElementById("root");
    window.scrollTo({ top: window.scrollY, behavior: "instant" });   /* stop a smooth scroll still under way */
    const place = () => {
      const y = window.scrollY, vh = window.innerHeight, rest = Math.max(0, root.offsetHeight - y - vh);
      root.style.setProperty("--sl-portal-y", y + vh / 2 + "px");
      root.style.setProperty("--sl-portal-scroll", y + "px");
      root.style.setProperty("--sl-portal-clip", "inset(" + y + "px 0 " + rest + "px 0 round 22px)");
    };
    root.style.setProperty("--sl-portal-bg", getComputedStyle(document.body).backgroundColor);   /* the page keeps its own background */
    place();
    html.classList.add("sl-portal-desk");
    /* If the page still moves (an ending smooth scroll, scroll anchoring), the screen follows it */
    window.addEventListener("scroll", place, { passive: true });
    return () => { window.removeEventListener("scroll", place); unshrink(); };
  }, []);
  useEffect(() => { if (variant === "desk" && phase === "done") unshrink(); }, [phase]);

  /* Fetch and compile the code */
  useEffect(() => {
    if (phase !== "load") return;
    let alive = true;
    slLoad(files).then(() => { if (alive) setPhase("build"); }).catch((e) => {
      console.error("[lazy-load]", e);
      if (!alive) return;
      setFailed(true); setTimeout(() => closeRef.current(), 1800);
    });
    return () => { alive = false; };
  }, []);

  /* Wait for the view to report ready, then reveal it */
  useEffect(() => {
    if (phase !== "build") return;
    const host = hostRef.current;
    let timer = 0;
    const check = () => {
      if (!host || !host.querySelector(readySelector)) return false;
      mo.disconnect();
      timer = setTimeout(() => setPhase("reveal"), Math.max(0, PORTAL_MIN_MS - (performance.now() - t0.current)));
      return true;
    };
    const mo = new MutationObserver(check);
    if (!check()) mo.observe(host, { subtree: true, childList: true, attributes: true, attributeFilter: ["class"] });
    return () => { mo.disconnect(); clearTimeout(timer); };
  }, [phase]);
  useEffect(() => {
    if (phase !== "reveal") return;
    const t = setTimeout(() => setPhase("done"), 700);
    return () => clearTimeout(t);
  }, [phase]);

  /* Esc backs out while the transition is showing (the view handles it after that) */
  useEffect(() => {
    if (phase === "done" || phase === "reveal") return;
    const onKey = (e) => { if (e.key === "Escape") closeRef.current(); };
    document.addEventListener("keydown", onKey);
    return () => document.removeEventListener("keydown", onKey);
  }, [phase]);

  const shown = phase === "reveal" || phase === "done";
  return (
    <React.Fragment>
      {phase !== "load" && (
        <div ref={hostRef} className={"sl-portal__host sl-portal__host--" + variant + (shown ? " is-shown" : "")}>{children()}</div>
      )}
      {phase !== "done" && (
        <div className={"sl-portal sl-portal--" + variant + (variant === "street" && portalIsNight() ? " is-night" : "") + (phase === "reveal" ? " is-leaving" : "")} role="status" aria-live="polite">
          {variant === "desk" ? <div className="sl-portal__screen" aria-hidden="true"><i></i></div> : <PortalSkyline/>}
          <div className="sl-portal__caption">
            <span className="sl-portal__kicker">{T.kicker}{T.place && <em> · {T.place}</em>}</span>
            <span className={"sl-portal__line" + (failed ? " is-failed" : "")} aria-hidden="true"><i></i></span>
            <span className="sl-portal__step">{failed ? DESK_TEXT.portal.failed : phase === "load" ? T.steps[0] : T.steps[1]}</span>
          </div>
        </div>
      )}
    </React.Fragment>
  );
}

/* The secret switch: a tiny monitor glyph next to the menu button */
function DeskToggle() {
  const [open, setOpen] = useState(false);
  const [ok] = useState(deskSupported);
  if (!ok) return null;
  /* Hover or focus is a good hint a click is coming: start downloading (not compiling) the office and three.js */
  const warm = () => { slPrefetch(DESK_FILES); if (!window.THREE && typeof THREE_SRC === "string") slPrefetchUrl(THREE_SRC); };
  const close = () => setOpen(false);
  return (
    <React.Fragment>
      <button className="sl-desk-toggle" onClick={() => setOpen(true)} onPointerEnter={warm} onFocus={warm} aria-label={DESK_TEXT.toggle} title={DESK_TEXT.toggle}>
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <rect x="3.5" y="4.5" width="17" height="11" rx="1.6" fill="none" stroke="currentColor" strokeWidth="1.6"/>
          <path d="M9 19.5h6M12 15.5v4" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/>
        </svg>
      </button>
      {open && ReactDOM.createPortal(
        <LazyView files={DESK_FILES} isLoaded={typeof DeskMode === "function"} variant="desk" readySelector=".sl-desk.is-ready" onClose={close}>
          {() => <DeskMode onClose={close}/>}
        </LazyView>, document.body)}
    </React.Fragment>
  );
}
