/* ============ MAIN OS ORCHESTRATOR ============ */
/* global React, ReactDOM, ScreenOff, ScreenStandby, BootSequence, AppWindow, Taskbar, StartMenu, Spotlight, ICON,
   APP_REGISTRY, AboutApp, ServicesApp, PortfolioApp, ProcessApp, TeamApp,
   ContactApp, TerminalApp, ReadmeApp, MusicApp, DoomApp, PrzelomAudio,
   SettingsApp, FilesApp, BrowserApp, AILabApp, StatsApp, CalendarApp, YouTubeApp, YouTubePlayerApp, AgentApp,
   useTweaks, TweaksPanel, TweakSection, TweakColor, TweakSlider, TweakRadio, TweakSelect */

// Aplikacje wymagające zalogowania — guest dostaje AuthWall
const LOCKED_APPS = new Set(["prostudio", "youtube", "browser", "upgrade"]);

// Globalne flagi tier'a — czytane bezpośrednio z window przez różne aplikacje.
// Aktualizowane reaktywnie przy zmianie sesji albo planu (PRO upgrade w trakcie pracy).
function recomputeGlobals() {
  const tier = window.AUTH?.getActiveTier?.()?.tier || "free";
  window.PERSONAL_ACTIVE = !!window.AUTH?.user;
  window.CURRENT_TIER = tier; // 'free' | 'pro' | 'epic'
  window.PRO_MODE_ACTIVE = tier === "pro" || tier === "epic";
  window.EPIC_ACTIVE = tier === "epic";
}
recomputeGlobals();
if (window.AUTH?.onChange) {
  window.AUTH.onChange(() => recomputeGlobals());
}

const APP_COMPONENTS = {
  about: AboutApp,
  services: ServicesApp,
  portfolio: PortfolioApp,
  process: ProcessApp,
  team: TeamApp,
  contact: ContactApp,
  terminal: TerminalApp,
  readme: ReadmeApp,
  music: MusicApp,
  doom: DoomApp,
  settings: SettingsApp,
  files: FilesApp,
  browser: BrowserApp,
  ai: AILabApp,
  stats: StatsApp,
  calendar: CalendarApp,
  notepad: NotepadApp,
  youtube: YouTubeApp,
  ytplayer: YouTubePlayerApp,
  upgrade: ProUpgradeApp,
  account: ProAccountApp,
  prostudio: ProStudioApp,
  agent: AgentApp,
};

const THEME_ACCENT = {
  "neon-lime": "#caff33",
  "cyber-pink": "#ff66cc",
  "ice-cyan": "#33ffe1",
  "solar-amber": "#ffcc33",
  bloodmoon: "#ff3344",
  matrix: "#22ff66",
  phosphor: "#ffaa00",
  lavender: "#b58aff",
};

function loadLocalPrefs() {
  try {
    return JSON.parse(localStorage.getItem("przelom-prefs") || "{}");
  } catch {
    return {};
  }
}
// Read prefs preferring Redis-synced (window.AUTH.prefs.os) if user is logged in,
// otherwise fall back to localStorage. This is called on initial mount; subsequent
// updates after login arrive via the AUTH.onChange listener.
function loadPrefs() {
  const local = loadLocalPrefs();
  const remote = window.AUTH?.user ? window.AUTH?.prefs?.os : null;
  if (remote && typeof remote === "object") return { ...local, ...remote };
  return local;
}
function savePrefs(p) {
  try {
    localStorage.setItem("przelom-prefs", JSON.stringify(p));
  } catch {}
  // If logged in — also sync to Redis (debounced 500ms in AUTH.savePrefs).
  // Namespace under "os" so other prefs (desktop layout, agent tokens, etc.) stay isolated.
  if (window.AUTH?.user && window.AUTH?.savePrefs) {
    try {
      window.AUTH.savePrefs({ os: p });
    } catch {}
  }
}

// ============ Global Bus (AI agent → OS actions) ============
window.PrzelomBus = (() => {
  const listeners = {};
  return {
    on(event, cb) {
      (listeners[event] ||= []).push(cb);
      return () => {
        listeners[event] = listeners[event].filter((c) => c !== cb);
      };
    },
    emit(event, arg) {
      (listeners[event] || []).forEach((cb) => {
        try {
          cb(arg);
        } catch (e) {
          console.warn("bus error", e);
        }
      });
    },
  };
})();

function PrzelomOS() {
  const defaults = window.TWEAK_DEFAULTS || {};
  const stored = loadPrefs();
  const initTheme = stored.theme || defaults.theme || "neon-lime";
  const [tweaks, setTweak] = useTweaks({
    accent: THEME_ACCENT[initTheme] || defaults.accent || "#caff33",
    crtIntensity: stored.crtIntensity ?? defaults.crtIntensity ?? 1,
    particles: stored.particles ?? defaults.particles ?? 200,
    gameDifficulty:
      stored.gameDifficulty ?? defaults.gameDifficulty ?? "normal",
    theme: initTheme,
    // Migration: jeśli user miał zapisane usunięte tapety (glitch) — fallback do synthwave
    wallpaper: (() => {
      const VALID = [
        "synthwave",
        "matrix",
        "stars",
        "plasma",
        "sunset",
        "ocean",
        "grid",
        "aurora",
        "circuit",
      ];
      const w = stored.wallpaper || defaults.wallpaper || "synthwave";
      return VALID.includes(w) ? w : "synthwave";
    })(),
  });

  // Phase machine: 'off' → 'standby' → 'booting' → 'desktop'
  const [phase, setPhase] = useState("off");
  const [zoomT, setZoomT] = useState(0);
  const [windows, setWindows] = useState([]);
  const [zMax, setZMax] = useState(100);
  const [startOpen, setStartOpen] = useState(false);
  const [spotlightOpen, setSpotlightOpen] = useState(false);
  const [tweaksMode, setTweaksMode] = useState(false);
  const [soundOn, setSoundOn] = useState(stored.soundOn ?? true);
  const [volume, setVolume] = useState(stored.volume ?? 0.3);

  // PRO mode state — kto się loguje przy starcie
  const [proMode, setProMode] = useState(false); // czy boot poszedł w pro
  const [proLoginOpen, setProLoginOpen] = useState(false);
  const [proUser, setProUserState] = useState(window.PRO?.loadUser?.() || null);
  const proActiveRef = useRef(window.PRO?.isActive?.() || false);
  // PRO active = user zalogowany (window.AUTH) + ma opłacony plan PRO.
  // proMode (legacy boot flag) nie jest już wymagany.
  const proActive = !!window.AUTH?.user && (window.PRO?.isActive?.() || false);
  const [toasts, setToasts] = useState([]);

  // Track screen size dla window constraint
  const screenSize = useRef({ w: 800, h: 600 });
  useEffect(() => {
    const update = () => {
      // W fazie desktop overlay jest fullscreenowy (calc(100vw-20px)) — czytamy
      // realny rozmiar bezpośrednio z elementu, żeby okna mogły dojechać do
      // prawej krawędzi. W fazie 3D fallback do CSS var z projekcji monitora.
      const overlay = document.getElementById("screen-overlay");
      if (overlay) {
        const r = overlay.getBoundingClientRect();
        if (r.width > 0 && r.height > 0) {
          screenSize.current = { w: r.width, h: r.height };
          return;
        }
      }
      const root = getComputedStyle(document.documentElement);
      screenSize.current = {
        w: parseFloat(root.getPropertyValue("--screen-w")) || 800,
        h: parseFloat(root.getPropertyValue("--screen-h")) || 600,
      };
    };
    const id = setInterval(update, 200);
    update();
    const onResize = () => update();
    window.addEventListener("resize", onResize);
    return () => {
      clearInterval(id);
      window.removeEventListener("resize", onResize);
    };
  }, []);

  const pushToast = useCallback((title, msg) => {
    const id = Date.now() + Math.random();
    setToasts((t) => [...t, { id, title, msg }]);
    setTimeout(() => setToasts((t) => t.filter((x) => x.id !== id)), 3500);
  }, []);

  // CSS theme + accent
  useEffect(() => {
    document.documentElement.style.setProperty("--accent", tweaks.accent);
    document.documentElement.style.setProperty(
      "--crt-intensity",
      tweaks.crtIntensity,
    );
    document.documentElement.dataset.theme = tweaks.theme;
  }, [tweaks.accent, tweaks.crtIntensity, tweaks.theme]);

  // Three.js sync
  useEffect(() => {
    if (window.PrzelomScene) {
      window.PrzelomScene.setAccent(tweaks.accent);
      window.PrzelomScene.setParticles(tweaks.particles);
      window.PrzelomScene.setCrtIntensity(tweaks.crtIntensity);
    }
  }, [tweaks.accent, tweaks.particles, tweaks.crtIntensity]);

  // Sound state
  useEffect(() => {
    PrzelomAudio.setMuted(!soundOn);
    PrzelomAudio.setVolume(volume);
  }, [soundOn, volume]);

  // Capture ?agent=XXXX-XXXX from CLI deep link as soon as the OS mounts.
  // The user lands on this URL while logged out (LED phase), so we stash the
  // code in localStorage and the desktop-phase effect below opens KONTO PRO
  // and auto-submits it once they finish logging in.
  useEffect(() => {
    try {
      const u = new URL(window.location.href);
      const q = u.searchParams.get("agent");
      if (q && /^[A-Z0-9-]{4,16}$/i.test(q)) {
        localStorage.setItem("__pz_agent_pending", q.toUpperCase().trim());
        u.searchParams.delete("agent");
        window.history.replaceState({}, "", u.toString());
      }
    } catch {}
  }, []);

  // Save prefs (localStorage immediately + Redis debounced 500ms when logged in)
  useEffect(() => {
    savePrefs({
      theme: tweaks.theme,
      wallpaper: tweaks.wallpaper,
      crtIntensity: tweaks.crtIntensity,
      particles: tweaks.particles,
      gameDifficulty: tweaks.gameDifficulty,
      soundOn,
      volume,
    });
  }, [
    tweaks.theme,
    tweaks.wallpaper,
    tweaks.crtIntensity,
    tweaks.particles,
    tweaks.gameDifficulty,
    soundOn,
    volume,
  ]);

  // Cross-device prefs sync — when AUTH state changes (login/refresh from another
  // device), pull os.* from Redis and apply to React state. On first login from
  // a new device with empty server prefs, push local prefs to Redis (migration).
  useEffect(() => {
    if (!window.AUTH?.onChange) return;
    const applyRemote = () => {
      const u = window.AUTH?.user;
      const remote = u ? window.AUTH?.prefs?.os : null;
      if (u && (!remote || Object.keys(remote || {}).length === 0)) {
        // Migration: server has nothing, push current local prefs.
        const localOs = loadLocalPrefs();
        if (localOs && Object.keys(localOs).length > 0) {
          try {
            window.AUTH.savePrefs?.({ os: localOs });
          } catch {}
        }
        return;
      }
      if (!remote) return;
      if (remote.theme) {
        setTweak("theme", remote.theme);
        setTweak("accent", THEME_ACCENT[remote.theme] || "#caff33");
      }
      if (remote.wallpaper) setTweak("wallpaper", remote.wallpaper);
      if (remote.crtIntensity != null)
        setTweak("crtIntensity", remote.crtIntensity);
      if (remote.particles != null) setTweak("particles", remote.particles);
      if (remote.gameDifficulty)
        setTweak("gameDifficulty", remote.gameDifficulty);
      if (remote.soundOn != null) setSoundOn(remote.soundOn);
      if (remote.volume != null) setVolume(remote.volume);
    };
    // Apply once on mount in case AUTH was hydrated before this effect ran.
    if (window.AUTH.user) applyRemote();
    return window.AUTH.onChange(applyRemote);
  }, [setTweak]);

  // Body data-phase attribute (CSS hooks)
  useEffect(() => {
    document.body.dataset.phase = phase;
    // Update three-scene screen mode + LED
    if (window.PrzelomScene) {
      if (phase === "off") {
        window.PrzelomScene.setLedColor(0xff2222);
        window.PrzelomScene.setScreenMode("off");
      } else if (phase === "standby") {
        window.PrzelomScene.setLedColor(0x22ff66);
        window.PrzelomScene.setScreenMode("standby");
      } else {
        // booting / desktop
        window.PrzelomScene.setLedColor(parseInt(tweaks.accent.slice(1), 16));
        window.PrzelomScene.setScreenMode("on");
      }
    }
    // Make screen overlay interactive only when ON
    const so = document.getElementById("screen-overlay");
    if (so) so.classList.toggle("interactive", phase !== "off");
  }, [phase, tweaks.accent]);

  // Proportional UI scaling — in desktop phase the overlay is fullscreen, so we
  // override --screen-w/--screen-h to the real viewport dimensions. This keeps
  // every existing calc(var(--screen-w)/N) rule scaling identically across
  // 1366×768, 1920×1080, 2560×1440 and 4K. --ui-scale is a generic multiplier
  // for code that wants explicit scaling (relative to a 1920×1080 baseline).
  useEffect(() => {
    if (phase !== "desktop" && phase !== "transitioning") return;
    const BASE_W = 1920;
    const BASE_H = 1080;
    const apply = () => {
      const vw = window.innerWidth;
      const vh = window.innerHeight;
      const screenW = Math.max(0, vw - 20);
      const screenH = Math.max(0, vh - 46);
      const root = document.documentElement.style;
      root.setProperty("--screen-w", screenW + "px");
      root.setProperty("--screen-h", screenH + "px");
      root.setProperty("--screen-left", "10px");
      root.setProperty("--screen-top", "10px");
      const scale = Math.max(
        0.6,
        Math.min(2.5, Math.min(vw / BASE_W, vh / BASE_H)),
      );
      root.setProperty("--ui-scale", scale.toFixed(3));
    };
    apply();
    window.addEventListener("resize", apply);
    return () => window.removeEventListener("resize", apply);
  }, [phase]);

  // Adaptive performance — wait for PrzelomScene then init perf detector +
  // FPS auto-tuner. Toast on auto-downgrade so users can find the manual
  // override in Settings if they want to lock back to a higher profile.
  useEffect(() => {
    let cancelled = false;
    const tryInit = () => {
      if (cancelled) return;
      if (window.PrzelomScene && window.PrzelomPerf) {
        try {
          window.PrzelomPerf.init();
        } catch (e) {
          console.warn("[perf] init failed", e);
        }
        return;
      }
      setTimeout(tryInit, 120);
    };
    tryInit();
    const onDown = (e) => {
      const d = e.detail || {};
      pushToast(
        "WYDAJNOŚĆ",
        `Tryb obniżony: ${d.from || "?"} → ${d.to || "?"}. Zmień w USTAWIENIACH.`,
      );
    };
    window.addEventListener("perf-auto-downgraded", onDown);
    return () => {
      cancelled = true;
      window.removeEventListener("perf-auto-downgraded", onDown);
      try {
        window.PrzelomPerf?.stopFpsMonitor?.();
      } catch {}
    };
  }, [pushToast]);

  // SystemSettings exposed for Settings app
  useEffect(() => {
    window.SystemSettings = {
      theme: tweaks.theme,
      wallpaper: tweaks.wallpaper,
      crtIntensity: tweaks.crtIntensity,
      particles: tweaks.particles,
      soundOn,
      volume,
      setTheme: (t) => {
        setTweak("theme", t);
        setTweak("accent", THEME_ACCENT[t] || "#caff33");
        pushToast("THEME", t);
      },
      setWallpaper: (w) => {
        setTweak("wallpaper", w);
        pushToast("WALLPAPER", w);
      },
      setCrtIntensity: (v) => setTweak("crtIntensity", v),
      setParticles: (v) => setTweak("particles", v),
      setSoundOn: (b) => {
        setSoundOn(b);
        pushToast("AUDIO", b ? "ON" : "OFF");
      },
      setVolume: (v) => setVolume(v),
      // Adaptive performance bridge — exposes the perf module to SettingsApp
      // without forcing a hard import. Returns null until perf.js has loaded.
      getPerf: () =>
        window.PrzelomPerf ? window.PrzelomPerf.getState() : null,
      setPerfProfile: (profile, mode) => {
        if (window.PrzelomPerf?.setProfile) {
          window.PrzelomPerf.setProfile(profile, mode);
          pushToast(
            "WYDAJNOŚĆ",
            `${profile.toUpperCase()} (${mode === "auto" ? "auto" : "ręcznie"})`,
          );
        }
      },
    };
  }, [tweaks, soundOn, volume, setTweak, pushToast]);

  // ============ LED click — power on (off → standby) ============
  useEffect(() => {
    if (!window.PrzelomScene) {
      // Wait for three-scene to load
      const id = setInterval(() => {
        if (window.PrzelomScene) {
          clearInterval(id);
          window.PrzelomScene.onLedClick(() => {
            setPhase((p) => {
              if (p === "off") {
                PrzelomAudio.init();
                PrzelomAudio.ensureRunning();
                PrzelomAudio.sounds.click();
                const start = performance.now();
                const dur = 700;
                const step = (now) => {
                  const t = Math.min((now - start) / dur, 1);
                  const eased = 1 - Math.pow(1 - t, 3);
                  const z = eased * 0.55;
                  setZoomT(z);
                  if (window.PrzelomScene) window.PrzelomScene.setZoom(z);
                  if (t < 1) requestAnimationFrame(step);
                };
                requestAnimationFrame(step);
                return "standby";
              }
              return p;
            });
          });
          // initial state sync
          window.PrzelomScene.setLedColor(0xff2222);
          window.PrzelomScene.setScreenMode("off");
        }
      }, 100);
      return () => clearInterval(id);
    } else {
      window.PrzelomScene.onLedClick(() => {
        setPhase((p) => {
          if (p === "off") {
            PrzelomAudio.init();
            PrzelomAudio.ensureRunning();
            PrzelomAudio.sounds.click();
            // Zoom częściowy → większy widok ekranu logowania
            const start = performance.now();
            const dur = 700;
            const step = (now) => {
              const t = Math.min((now - start) / dur, 1);
              const eased = 1 - Math.pow(1 - t, 3);
              const z = eased * 0.55;
              setZoomT(z);
              if (window.PrzelomScene) window.PrzelomScene.setZoom(z);
              if (t < 1) requestAnimationFrame(step);
            };
            requestAnimationFrame(step);
            return "standby";
          }
          return p;
        });
      });
      window.PrzelomScene.setLedColor(0xff2222);
      window.PrzelomScene.setScreenMode("off");
    }
  }, []);

  // ============ Boot (zoom + boot screen) — generyczne ============
  const startBoot = useCallback(
    (isPro = false) => {
      PrzelomAudio.sounds.open();
      setProMode(isPro);
      // Jeśli pro: load user prefs (theme, wallpaper) — persistowane per user
      if (isPro) {
        const userPrefs = window.PRO?.loadUserPrefs?.();
        if (userPrefs) {
          if (userPrefs.theme) {
            setTweak("theme", userPrefs.theme);
            setTweak("accent", THEME_ACCENT[userPrefs.theme] || "#caff33");
          }
          if (userPrefs.wallpaper) setTweak("wallpaper", userPrefs.wallpaper);
        }
      }
      setPhase("booting");
      const start = performance.now();
      const dur = 900;
      const startZoom = zoomT; // kontynuuj od bieżącego zoomu (po standby = 0.55)
      const step = (now) => {
        const t = Math.min((now - start) / dur, 1);
        const eased = t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
        const z = startZoom + (1 - startZoom) * eased;
        setZoomT(z);
        if (window.PrzelomScene) window.PrzelomScene.setZoom(z);
        if (t < 1) requestAnimationFrame(step);
      };
      requestAnimationFrame(step);
    },
    [setTweak, zoomT],
  );

  // ============ Strefa CLIENT — natychmiast boot ============
  const startClientBoot = useCallback(() => startBoot(false), [startBoot]);

  // ============ Strefa PRO — najpierw login modal ============
  const startProFlow = useCallback(() => {
    PrzelomAudio.sounds.click();
    setProLoginOpen(true);
  }, []);

  const handleProLogin = useCallback(
    (user) => {
      setProUserState(user);
      setProLoginOpen(false);
      // Odpalamy boot w trybie pro
      startBoot(true);
    },
    [startBoot],
  );

  // ============ Shutdown — desktop → off (z fade) ============
  const shutdown = useCallback(() => {
    PrzelomAudio.sounds.shutdown();
    const fade = document.getElementById("fade-overlay");
    if (fade) fade.classList.add("on");
    setTimeout(() => {
      // Po fade out — kamera wraca do idle
      const start = performance.now();
      const dur = 600;
      const startT = zoomT;
      const step = (now) => {
        const t = Math.min((now - start) / dur, 1);
        const eased = 1 - Math.pow(1 - t, 3);
        const z = startT * (1 - eased);
        setZoomT(z);
        if (window.PrzelomScene) window.PrzelomScene.setZoom(z);
        if (t < 1) requestAnimationFrame(step);
        else {
          setPhase("off");
          setWindows([]);
          setStartOpen(false);
          setSpotlightOpen(false);
          setTimeout(() => {
            if (fade) fade.classList.remove("on");
          }, 100);
        }
      };
      requestAnimationFrame(step);
    }, 380);
  }, [zoomT]);

  // ============ Keyboard shortcuts ============
  useEffect(() => {
    const onKey = (e) => {
      if ((e.ctrlKey || e.metaKey) && e.key === "k") {
        e.preventDefault();
        if (phase === "desktop") setSpotlightOpen((s) => !s);
      }
      if (e.code === "Escape" && phase === "desktop") {
        if (spotlightOpen) {
          setSpotlightOpen(false);
          return;
        }
        if (startOpen) {
          setStartOpen(false);
          return;
        }
        const doomFocused = windows.find(
          (w) => w.id === "doom" && w.focused && !w.minimized,
        );
        if (!doomFocused) shutdown();
      }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [phase, spotlightOpen, startOpen, windows, shutdown]);

  // ============ Window manager ============
  // appId — id aplikacji (= id okna w domyśle)
  // opts.instanceId — gdy chcesz wiele okien tej samej aplikacji (np. notatnik)
  // opts.title — override title (np. nazwa pliku)
  // opts.props — extra props przekazywane do komponentu aplikacji
  const launchApp = useCallback(
    (appId, opts = {}) => {
      const meta = APP_REGISTRY.find((a) => a.id === appId);
      if (!meta) return;
      const winId = opts.instanceId || appId;
      setWindows((prev) => {
        const existing = prev.find((w) => w.id === winId);
        if (existing) {
          return prev.map((w) =>
            w.id === winId
              ? {
                  ...w,
                  minimized: false,
                  z: zMax + 1,
                  focused: true,
                  ...(opts.props ? { props: opts.props } : {}),
                  ...(opts.title ? { title: opts.title } : {}),
                }
              : { ...w, focused: false },
          );
        }
        // Otwieramy okna wyśrodkowane. Drobny offset gdy już są okna otwarte —
        // żeby nie nakładały się idealnie i dało się je rozróżnić.
        const sw = screenSize.current.w;
        const sh = screenSize.current.h;
        const winW = Math.min(meta.size.w, sw - 20);
        const winH = Math.min(meta.size.h, sh - 50);
        const cascadeOffset = (prev.length * 18) % 90;
        const cx = Math.round((sw - winW) / 2);
        const cy = Math.round((sh - winH) / 2 - 12); // -12: nieco wyżej, optycznie centrum
        const px = Math.max(10, Math.min(cx + cascadeOffset, sw - winW - 10));
        const py = Math.max(10, Math.min(cy + cascadeOffset, sh - winH - 40));
        const nw = {
          id: winId,
          appId: appId,
          title: opts.title || meta.title,
          icon: meta.icon,
          pos: { x: px, y: py },
          size: { w: winW, h: winH },
          minimized: false,
          maximized: false,
          z: zMax + 1,
          focused: true,
          minW: 280,
          minH: 180,
          fixedSize: !!meta.fixedSize,
          props: opts.props || null,
        };
        PrzelomAudio.sounds.open();
        return [...prev.map((w) => ({ ...w, focused: false })), nw];
      });
      setZMax((z) => z + 1);
    },
    [zMax],
  );

  const closeWin = useCallback((id) => {
    PrzelomAudio.sounds.close();
    setWindows((prev) => prev.filter((w) => w.id !== id));
  }, []);
  const minWin = useCallback((id) => {
    PrzelomAudio.sounds.click();
    setWindows((prev) =>
      prev.map((w) =>
        w.id === id ? { ...w, minimized: true, focused: false } : w,
      ),
    );
  }, []);
  const maxWin = useCallback((id) => {
    PrzelomAudio.sounds.click();
    setWindows((prev) =>
      prev.map((w) => (w.id === id ? { ...w, maximized: !w.maximized } : w)),
    );
  }, []);
  const focusWin = useCallback(
    (id) => {
      setWindows((prev) => {
        const w = prev.find((x) => x.id === id);
        if (!w) return prev;
        const newZ = zMax + 1;
        return prev.map((x) =>
          x.id === id
            ? { ...x, z: newZ, focused: true, minimized: false }
            : { ...x, focused: false },
        );
      });
      setZMax((z) => z + 1);
    },
    [zMax],
  );

  // ============ AI BUS handlers ============
  useEffect(() => {
    const off1 = window.PrzelomBus.on("open", (appId) => {
      if (phase === "desktop") launchApp(appId);
      else pushToast("AI", "Najpierw uruchom system");
    });
    const off2 = window.PrzelomBus.on("close", (appId) => closeWin(appId));
    const off3 = window.PrzelomBus.on("focus", (appId) => focusWin(appId));
    const off4 = window.PrzelomBus.on("theme", (t) => {
      if (THEME_ACCENT[t]) {
        setTweak("theme", t);
        setTweak("accent", THEME_ACCENT[t]);
        pushToast("AGENT", `Theme → ${t}`);
      }
    });
    const off5 = window.PrzelomBus.on("wallpaper", (w) => {
      setTweak("wallpaper", w);
      pushToast("AGENT", `Wallpaper → ${w}`);
    });
    const off6 = window.PrzelomBus.on("shutdown", () => shutdown());
    const off7 = window.PrzelomBus.on("sound", (v) => {
      setSoundOn(v === "on");
      pushToast("AGENT", `Sound ${v}`);
    });
    const off8 = window.PrzelomBus.on("openNotepad", (file) => {
      const instanceId = "notepad-" + (file.name || Date.now());
      launchApp("notepad", {
        instanceId,
        title: file.name || "Notatnik",
        props: { file },
      });
    });
    const offYT = window.PrzelomBus.on("openYTPlayer", (video) => {
      if (!video?.id) return;
      launchApp("ytplayer", {
        instanceId: "ytplayer-" + video.id,
        title: (video.title || "YT").slice(0, 60),
        props: { video },
      });
    });
    // Akcja agenta: youtube:QUERY — wyszukaj i odtwórz pierwszy wynik w playerze
    const offYTSearch = window.PrzelomBus.on("youtube", async (query) => {
      const q = (query || "").trim();
      if (!q) return;
      if (phase !== "desktop") {
        pushToast("AI", "Najpierw uruchom system");
        return;
      }
      pushToast("AGENT", "Szukam: " + q.slice(0, 40));
      try {
        const res = await fetch(
          "/api/youtube-search?q=" + encodeURIComponent(q),
        );
        const data = await res.json();
        if (!res.ok || !data.results?.length) {
          pushToast("AGENT", "Brak wyników dla: " + q.slice(0, 30));
          return;
        }
        const video = data.results[0];
        launchApp("ytplayer", {
          instanceId: "ytplayer-" + video.id,
          title: (video.title || "YT").slice(0, 60),
          props: { video },
        });
      } catch (e) {
        pushToast("AGENT", "Błąd YT: " + e.message);
      }
    });
    // Agentic action: browse:URL — sanityzuje, otwiera BrowserApp i nawiguje na URL
    const sanitizeUrl = (raw) => {
      if (!raw || typeof raw !== "string") return null;
      let url = raw.trim();
      if (!/^https?:\/\//i.test(url)) url = "https://" + url;
      try {
        const u = new URL(url);
        if (u.protocol !== "http:" && u.protocol !== "https:") return null;
        return u.toString();
      } catch {
        return null;
      }
    };
    const off9 = window.PrzelomBus.on("browse", (rawUrl) => {
      const url = sanitizeUrl(rawUrl);
      if (!url) {
        pushToast("AGENT", "Nieprawidłowy URL");
        return;
      }
      if (phase === "desktop") {
        // Otwórz BrowserApp z initialUrl (każde wywołanie = nowa instancja → świeża nawigacja)
        const instanceId = "browser-main";
        launchApp("browser", {
          instanceId,
          title: "Przeglądarka",
          props: { initialUrl: url },
        });
        // Jeśli już otwarte — emitnij event na nawigację
        setTimeout(() => {
          if (window.PrzelomBus)
            window.PrzelomBus.emit("browser-navigate", url);
        }, 50);
      } else {
        pushToast("AI", "Najpierw uruchom system");
      }
    });
    return () => {
      off1();
      off2();
      off3();
      off4();
      off5();
      off6();
      off7();
      off8();
      off9();
      offYT();
      offYTSearch();
    };
  }, [launchApp, closeWin, focusWin, setTweak, pushToast, phase, shutdown]);

  // Welcome toast at first desktop entry
  const welcomedRef = useRef(false);
  useEffect(() => {
    if (phase === "desktop" && !welcomedRef.current) {
      welcomedRef.current = true;
      setTimeout(
        () =>
          pushToast("SYSTEM", "Ctrl+K = szukajka. Otwórz AI LAB by pogadać."),
        600,
      );
    }
  }, [phase, pushToast]);

  // After login on desktop, if a pending CLI agent code exists, auto-open
  // KONTO PRO so AgentZazaSection picks the code up and submits it. Without
  // this the user lands on the desktop after browser-login but never sees the
  // confirmation panel — and the CLI keeps polling forever.
  const agentPromptedRef = useRef(false);
  useEffect(() => {
    if (phase !== "desktop") return;
    if (agentPromptedRef.current) return;
    if (!window.AUTH?.user) return;
    let pending = null;
    try {
      pending = localStorage.getItem("__pz_agent_pending");
    } catch {}
    if (!pending) return;
    agentPromptedRef.current = true;
    setTimeout(() => {
      pushToast("AGENT ZAZA", `Autoryzacja CLI · kod ${pending}`);
      try {
        launchApp("account");
      } catch {}
    }, 900);
  }, [phase, pushToast, launchApp]);

  // Tweaks edit-mode
  useEffect(() => {
    const onMsg = (e) => {
      if (e.data?.type === "__activate_edit_mode") setTweaksMode(true);
      if (e.data?.type === "__deactivate_edit_mode") setTweaksMode(false);
    };
    window.addEventListener("message", onMsg);
    window.parent.postMessage({ type: "__edit_mode_available" }, "*");
    return () => window.removeEventListener("message", onMsg);
  }, []);

  const onBootDone = () => {
    // Fade out → switch to fullscreen → fade in → autostart AI LAB z losowym openerem
    const fade = document.getElementById("fade-overlay");
    if (fade) fade.classList.add("on");
    setPhase("transitioning");
    setTimeout(() => {
      setPhase("desktop");
      // ekran wskoczy w fullscreen przez CSS rule
      setTimeout(() => {
        if (fade) fade.classList.remove("on");
        // Auto-launch (zaraz po pełnym fade in)
        setTimeout(() => {
          if (window.pickAIOpener)
            window.PENDING_AI_OPENER = window.pickAIOpener();
          // PRO active = zalogowany user + opłacony plan PRO (lifetime/active).
          window.PRO_MODE_ACTIVE =
            !!window.AUTH?.user && (window.PRO?.isActive?.() || false);
          // Personalizacja przysługuje każdemu zalogowanemu (nie tylko PRO)
          window.PERSONAL_ACTIVE = !!window.AUTH?.user;

          // Autostart: lista aplikacji z prefs (zalogowani) lub default ['ai']
          const desktopPrefs = window.PERSONAL_ACTIVE
            ? window.PRO.loadDesktopPrefs()
            : null;
          const autostartApps =
            desktopPrefs?.autostart?.length > 0
              ? desktopPrefs.autostart
              : ["ai"];
          autostartApps.forEach((appId, i) => {
            setTimeout(() => launchApp(appId), i * 250);
          });
          // Toast ostrzeżenia gdy pro mode bez aktywnej subskrypcji
          if (proMode && proUser && !window.PRO_MODE_ACTIVE) {
            setTimeout(
              () =>
                pushToast(
                  "PRO",
                  "Brak aktywnej subskrypcji — funkcje ograniczone do read-only",
                ),
              800,
            );
          } else if (window.PRO_MODE_ACTIVE) {
            setTimeout(
              () =>
                pushToast(
                  "★ PRO",
                  "Witaj " +
                    (proUser?.name || proUser?.email || "") +
                    " — pełen dostęp aktywny",
                ),
              800,
            );
          }
        }, 400);
      }, 80);
    }, 380);
  };

  // Determine what to render INSIDE screen overlay
  let screenContent;
  if (phase === "off") {
    screenContent = <ScreenOff />;
  } else if (phase === "standby") {
    screenContent = <ScreenStandby onStartClient={startClientBoot} />;
  } else if (phase === "booting") {
    screenContent = <BootSequence onDone={onBootDone} />;
  } else if (phase === "transitioning") {
    // Pusty czarny ekran — fade-overlay nakrywa wszystko, screen się zmienia na fullscreen
    screenContent = (
      <div style={{ position: "absolute", inset: 0, background: "#000" }} />
    );
  } else if (phase === "desktop") {
    screenContent = (
      <Desktop
        windows={windows}
        launchApp={launchApp}
        closeWin={closeWin}
        minWin={minWin}
        maxWin={maxWin}
        focusWin={focusWin}
        startOpen={startOpen}
        setStartOpen={setStartOpen}
        spotlightOpen={spotlightOpen}
        setSpotlightOpen={setSpotlightOpen}
        shutdown={shutdown}
        wallpaper={tweaks.wallpaper}
        soundOn={soundOn}
        setSoundOn={setSoundOn}
        toasts={toasts}
        screenSize={screenSize}
        proMode={proMode}
        proUser={proUser}
        proActive={proActive}
      />
    );
  }

  // Use React Portal — single React tree, state propaguje od razu (np. zmiana tapety)
  const screenTarget = document.getElementById("screen-content");

  // Persistencja per user — PRO mode zapisuje theme/wallpaper na konto
  useEffect(() => {
    if (proMode && proUser && window.PRO?.saveUserPrefs) {
      window.PRO.saveUserPrefs({
        theme: tweaks.theme,
        wallpaper: tweaks.wallpaper,
      });
    }
  }, [proMode, proUser, tweaks.theme, tweaks.wallpaper]);

  return (
    <>
      {screenTarget && ReactDOM.createPortal(screenContent, screenTarget)}
      {proLoginOpen &&
        screenTarget &&
        ReactDOM.createPortal(
          <ProLoginModal
            onLogin={handleProLogin}
            onClose={() => setProLoginOpen(false)}
          />,
          screenTarget,
        )}
      {tweaksMode && (
        <TweaksPanel title="Tweaks" onClose={() => setTweaksMode(false)}>
          <TweakSection title="Look">
            <TweakColor
              label="Accent"
              value={tweaks.accent}
              onChange={(v) => setTweak("accent", v)}
            />
            <TweakSlider
              label="CRT intensity"
              min={0}
              max={1.5}
              step={0.05}
              value={tweaks.crtIntensity}
              onChange={(v) => setTweak("crtIntensity", v)}
            />
            <TweakSlider
              label="Particles"
              min={0}
              max={500}
              step={10}
              value={tweaks.particles}
              onChange={(v) => setTweak("particles", v)}
            />
          </TweakSection>
          <TweakSection title="Game">
            <TweakRadio
              label="Difficulty"
              value={tweaks.gameDifficulty}
              options={["easy", "normal", "hard"]}
              onChange={(v) => setTweak("gameDifficulty", v)}
            />
          </TweakSection>
        </TweaksPanel>
      )}
    </>
  );
}

// ============================================================
// Matrix wallpaper — osobny komponent React zarządzający canvas-em.
// React mountuje/unmountuje go automatycznie gdy wallpaper === 'matrix' się zmienia.
// Brak ręcznego DOM manipulation = brak błędów race condition.
// ============================================================
function MatrixWallpaperCanvas() {
  const ref = useRef(null);
  useEffect(() => {
    const c = ref.current;
    if (!c) return;
    const parent = c.parentElement;
    c.width = parent.offsetWidth;
    c.height = parent.offsetHeight;
    const ctx = c.getContext("2d");
    const cols = Math.floor(c.width / 12);
    const drops = Array(cols)
      .fill(0)
      .map(() => Math.random() * c.height);
    const chars = "アイウエオカキクケコZAZA0123456789";
    let raf = 0;
    let stopped = false;
    const draw = () => {
      if (stopped) return;
      ctx.fillStyle = "rgba(0,0,0,0.07)";
      ctx.fillRect(0, 0, c.width, c.height);
      ctx.font = "11px JetBrains Mono";
      ctx.fillStyle =
        getComputedStyle(document.documentElement).getPropertyValue(
          "--accent",
        ) || "#caff33";
      drops.forEach((y, i) => {
        const ch = chars[Math.floor(Math.random() * chars.length)];
        ctx.fillText(ch, i * 12, y);
        if (y > c.height && Math.random() > 0.97) drops[i] = 0;
        drops[i] += 12;
      });
      raf = requestAnimationFrame(draw);
    };
    draw();
    // Reaguj na resize parent (np. okno przeglądarki)
    const ro = new ResizeObserver(() => {
      if (!c.parentElement) return;
      c.width = c.parentElement.offsetWidth;
      c.height = c.parentElement.offsetHeight;
    });
    ro.observe(parent);
    return () => {
      stopped = true;
      cancelAnimationFrame(raf);
      ro.disconnect();
    };
  }, []);
  return (
    <canvas
      ref={ref}
      style={{
        position: "absolute",
        inset: 0,
        width: "100%",
        height: "100%",
        display: "block",
      }}
    />
  );
}

function Desktop({
  windows,
  launchApp,
  closeWin,
  minWin,
  maxWin,
  focusWin,
  startOpen,
  setStartOpen,
  spotlightOpen,
  setSpotlightOpen,
  shutdown,
  wallpaper,
  soundOn,
  setSoundOn,
  toasts,
  screenSize,
  proMode,
  proUser,
  proActive,
}) {
  const wallpaperRef = useRef(null);

  // AUTH state — guest vs zalogowany (lokalny state w Desktop, bo tu robimy gating)
  const [authUser, setAuthUser] = useState(window.AUTH?.user || null);
  useEffect(() => {
    if (!window.AUTH?.onChange) return;
    return window.AUTH.onChange((u) => {
      setAuthUser(u);
      // Cross-device: when refresh() pulls fresh prefs.desktop from Redis
      // for the same user (no authUser change), still reload desk layout.
      if (u) setDeskPrefs(window.PRO.loadDesktopPrefs());
    });
  }, []);

  // Personalizacja: każdy zalogowany ma pełną personalizację (jak PRO).
  // proActive zostaje dla rzeczy płatnych (PRO Studio, premium AI itp.).
  const personalActive = !!authUser;

  // Desktop personalizacja — ładuj prefs (drag positions, widgets, quick launch)
  const [deskPrefs, setDeskPrefs] = useState(() =>
    personalActive
      ? window.PRO.loadDesktopPrefs()
      : window.PRO.defaultDesktopPrefs(),
  );
  useEffect(() => {
    if (personalActive) setDeskPrefs(window.PRO.loadDesktopPrefs());
  }, [personalActive]);
  // Instant reakcja na zmiany prefs (z DesktopPrefsPanel itp.)
  useEffect(() => {
    const handler = (e) => {
      const next = e.detail || window.PRO.loadDesktopPrefs();
      setDeskPrefs(next);
    };
    window.addEventListener("desk-prefs-changed", handler);
    return () => window.removeEventListener("desk-prefs-changed", handler);
  }, []);
  const updateDeskPrefs = (partial) => {
    setDeskPrefs((prev) => {
      const next = { ...prev, ...partial };
      if (personalActive) window.PRO.saveDesktopPrefs(next);
      return next;
    });
  };

  // Mesh constants — używane do computowania default pozycji i snap-to-grid
  const ICON_W = 80,
    ICON_H = 86,
    GAP_X = 10,
    GAP_Y = 6;
  const CELL_W = ICON_W + GAP_X; // 90
  const CELL_H = ICON_H + GAP_Y; // 92
  const PAD = 16; // padding od krawędzi
  const TASKBAR_H = 36; // miejsce na taskbar (bottom)

  // Lista widocznych appek (po filtrach hidden i hiddenIcons z prefs)
  const visibleApps = APP_REGISTRY.filter((a) => {
    if (a.hidden) return false;
    if ((deskPrefs.hiddenIcons || []).includes(a.id)) return false;
    return true;
  });

  // Domyślne pozycje meszy (column-flow, top-down). Przelicz przy każdym
  // renderze — uwzględnia bieżący rozmiar ekranu, zmiany hiddenIcons itp.
  const defaultMeshPositions = (() => {
    const sh = screenSize.current?.h || 600;
    const usableH = Math.max(CELL_H, sh - 2 * PAD - TASKBAR_H);
    const rowsPerCol = Math.max(1, Math.floor(usableH / CELL_H));
    const map = {};
    visibleApps.forEach((a, idx) => {
      const col = Math.floor(idx / rowsPerCol);
      const row = idx % rowsPerCol;
      map[a.id] = { x: PAD + col * CELL_W, y: PAD + row * CELL_H };
    });
    return map;
  })();
  const effectivePos = (appId) =>
    deskPrefs.iconPositions?.[appId] || defaultMeshPositions[appId];

  // Drag handler dla ikon (zalogowani)
  const dragRef = useRef(null);
  const lastDragEndRef = useRef(0); // timestamp ostatniego dragu z ruchem — blokujemy click przez 300ms
  const onIconMouseDown = (appId, e) => {
    if (!personalActive) return; // guest nie może drag
    if (e.detail === 2) return; // dwuklik = open
    const target = e.currentTarget;
    const rect = target.getBoundingClientRect();
    dragRef.current = {
      appId,
      offsetX: e.clientX - rect.left,
      offsetY: e.clientY - rect.top,
      moved: false,
      startX: e.clientX,
      startY: e.clientY,
      el: target,
      prevPos: effectivePos(appId), // do swap-a po dropie
    };
    e.preventDefault();
  };
  const snapPos = (x, y, sRect) => {
    // snap do siatki względem PAD
    const sx = Math.round((x - PAD) / CELL_W) * CELL_W + PAD;
    const sy = Math.round((y - PAD) / CELL_H) * CELL_H + PAD;
    // bounds — cała ikona widoczna, nie za taskbar
    const maxX = sRect.width - ICON_W - PAD;
    const maxY = sRect.height - ICON_H - TASKBAR_H - PAD;
    return {
      x: Math.max(PAD, Math.min(maxX, sx)),
      y: Math.max(PAD, Math.min(maxY, sy)),
    };
  };

  useEffect(() => {
    if (!personalActive) return;
    const mv = (e) => {
      const d = dragRef.current;
      if (!d) return;
      // Ruch musi być min 4px żeby uznać za drag (drobne drgania myszy = NIE drag)
      const dx = e.clientX - d.startX;
      const dy = e.clientY - d.startY;
      if (!d.moved && dx * dx + dy * dy < 16) return;
      d.moved = true;
      const screen = document.querySelector(".desktop");
      if (!screen) return;
      const sRect = screen.getBoundingClientRect();
      const rawX = e.clientX - sRect.left - d.offsetX;
      const rawY = e.clientY - sRect.top - d.offsetY;
      // Live preview: bounds (ale BEZ snap) — żeby drag był smooth
      const maxX = sRect.width - ICON_W - PAD;
      const maxY = sRect.height - ICON_H - TASKBAR_H - PAD;
      d.el.style.left = Math.max(PAD, Math.min(maxX, rawX)) + "px";
      d.el.style.top = Math.max(PAD, Math.min(maxY, rawY)) + "px";
      d.lastRawX = rawX;
      d.lastRawY = rawY;
      d.lastRect = sRect;
    };
    const up = () => {
      const d = dragRef.current;
      if (!d) return;
      if (d.moved && d.lastRect) {
        // Snap przy puszczeniu — final position
        const snapped = snapPos(d.lastRawX, d.lastRawY, d.lastRect);
        d.el.style.left = snapped.x + "px";
        d.el.style.top = snapped.y + "px";
        const positions = { ...(deskPrefs.iconPositions || {}) };
        // SWAP: jeśli docelowy slot jest zajęty przez inną ikonę,
        // zamień miejsca (nie pozwala na overlap).
        const occupant = visibleApps.find(
          (a) =>
            a.id !== d.appId &&
            (() => {
              const p =
                a.id in positions ? positions[a.id] : effectivePos(a.id);
              return p && p.x === snapped.x && p.y === snapped.y;
            })(),
        );
        if (occupant) {
          positions[occupant.id] = d.prevPos;
        }
        positions[d.appId] = snapped;
        updateDeskPrefs({ iconPositions: positions });
        // Zablokuj nadchodzący click event (browser go odpali po mouseup)
        lastDragEndRef.current = Date.now();
      }
      dragRef.current = null;
    };
    window.addEventListener("mousemove", mv);
    window.addEventListener("mouseup", up);
    return () => {
      window.removeEventListener("mousemove", mv);
      window.removeEventListener("mouseup", up);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [personalActive, deskPrefs.iconPositions]);

  return (
    <div className="desktop">
      <div className="desktop-wallpaper" ref={wallpaperRef}>
        <div className={`dwp dwp-${wallpaper}`}></div>
        {wallpaper === "matrix" && <MatrixWallpaperCanvas />}
      </div>
      <div className="crt-tint"></div>

      {/* Desktop icons */}
      <div className="desktop-grid">
        {visibleApps.map((a) => {
          const Icon = ICON[a.icon];
          const isPro =
            a.id === "upgrade" || a.id === "account" || a.id === "prostudio";
          // Wszystkie ikony absolute-positioned na pozycji meszy.
          // customPos (po dragu) > defaultMeshPositions[id].
          const pos = effectivePos(a.id);
          const customStyle = pos
            ? {
                position: "absolute",
                left: pos.x,
                top: pos.y,
                margin: 0,
              }
            : {};
          return (
            <button
              key={a.id}
              className={`desk-icon ${isPro ? "desk-icon-pro" : ""} ${personalActive ? "desk-icon-draggable" : ""}`}
              style={customStyle}
              onMouseDown={(e) => onIconMouseDown(a.id, e)}
              onClick={(e) => {
                // Po dragu (z ruchem) browser triggeruje click — zablokuj go
                if (Date.now() - lastDragEndRef.current < 300) {
                  e.preventDefault();
                  e.stopPropagation();
                  return;
                }
                launchApp(a.id);
              }}
              onDoubleClick={() => launchApp(a.id)}
              title={
                a.desc +
                (personalActive ? " · trzymaj i przeciągnij aby ustawić" : "")
              }
            >
              <span className="desk-icon-img">{Icon ? <Icon /> : "◆"}</span>
              <span className="desk-icon-label">{a.title}</span>
            </button>
          );
        })}
      </div>

      {/* Quick launch widget — zalogowani czytają listę z prefs */}
      {(!personalActive || deskPrefs.widgets.quickLaunch) && (
        <div className="desktop-widget tl">
          <div className="widget-h">// QUICK_LAUNCH</div>
          {(personalActive
            ? deskPrefs.quickLaunchApps
            : ["prostudio", "ai", "portfolio", "stats", "doom", "contact"]
          ).map((appId) => {
            const meta = APP_REGISTRY.find((a) => a.id === appId);
            if (!meta) return null;
            const isPro = appId === "prostudio";
            return (
              <button
                key={appId}
                className={`widget-launch ${isPro ? "widget-launch-pro" : ""}`}
                onClick={() => launchApp(appId)}
              >
                {isPro ? "★ " : "◆ "}
                {meta.title} · {meta.desc.split(".")[0]}
              </button>
            );
          })}
        </div>
      )}

      {/* System log widget (top-right) — opcjonalny dla zalogowanych */}
      {(!personalActive || deskPrefs.widgets.syslog) && (
        <div className="desktop-widget tr">
          <div className="widget-h">// SYSTEM_LOG</div>
          <SysLog />
        </div>
      )}

      {/* System stats widget (bottom-left) — opcjonalny dla zalogowanych */}
      {(!personalActive || deskPrefs.widgets.agentMesh) && (
        <div className="desktop-widget bl">
          <div className="widget-h">// AGENT_MESH</div>
          <div className="widget-l">▣ 14/14 agentów online</div>
          <div className="widget-l">▣ uptime 99.7%</div>
          <div className="widget-l">▣ 4.8M docs in vector</div>
          <div className="widget-l">▣ 280h saved/m-c</div>
        </div>
      )}

      {windows.length === 0 && (
        <div className="desk-greet">
          <div className="desk-greet-tag">// ZAZA/OS · v3.0.1</div>
          <div className="desk-greet-h">
            Witaj w <em>przyszłości</em>.
          </div>
          <div className="desk-greet-sub">
            Dwuklik na ikonie pulpitu lub{" "}
            <strong style={{ color: "var(--accent)" }}>Ctrl+K</strong> aby
            uruchomić aplikację.
          </div>
        </div>
      )}

      {windows.map((w) => {
        const Comp = APP_COMPONENTS[w.appId || w.id];
        const meta = APP_REGISTRY.find((a) => a.id === (w.appId || w.id));
        const requiresAuth = LOCKED_APPS.has(w.appId || w.id);
        const showWall = requiresAuth && !authUser;
        const Wall = window.AuthWall;
        return (
          <AppWindow
            key={w.id}
            win={w}
            screenSize={screenSize}
            onClose={closeWin}
            onMin={minWin}
            onMaximize={maxWin}
            onFocus={focusWin}
          >
            {showWall ? (
              Wall ? (
                <Wall appLabel={meta?.title || ""} />
              ) : (
                <div style={{ padding: 20 }}>Ładowanie modułu auth…</div>
              )
            ) : (
              Comp && <Comp onShutdown={shutdown} {...(w.props || {})} />
            )}
          </AppWindow>
        );
      })}

      <Taskbar
        windows={windows}
        onFocus={focusWin}
        onClose={closeWin}
        onShutdown={shutdown}
        onToggleStart={() => setStartOpen(!startOpen)}
        startOpen={startOpen}
        soundOn={soundOn}
        onToggleSound={() => setSoundOn((s) => !s)}
        onSpotlight={() => setSpotlightOpen(true)}
        onSettings={() => launchApp("settings")}
      />

      {startOpen && (
        <StartMenu
          apps={APP_REGISTRY.filter((a) => !a.hidden)}
          onLaunch={launchApp}
          onClose={() => setStartOpen(false)}
          onShutdown={shutdown}
        />
      )}

      {spotlightOpen && (
        <Spotlight
          apps={APP_REGISTRY}
          onLaunch={launchApp}
          onClose={() => setSpotlightOpen(false)}
        />
      )}

      <div className="toast-stack">
        {toasts.map((t) => (
          <div key={t.id} className="toast">
            <div className="toast-h">// {t.title}</div>
            <div>{t.msg}</div>
          </div>
        ))}
      </div>

      <div className="crt-scanlines"></div>
      <div className="crt-vignette"></div>
    </div>
  );
}

function SysLog() {
  const [lines, setLines] = useState([
    "agent.scraper @ ok",
    "pipeline.invoice @ ok",
    "rag.vectorstore @ ok",
    "monitor.uptime @ ok",
    "queue.worker[3] @ ok",
    "cron.daily_etl @ ok",
  ]);
  useEffect(() => {
    const evts = [
      "cron.fired etl_daily",
      "agent.scraper run #2241",
      "rag.query +0.4s",
      "pipeline.invoice ok",
      "queue.flush 12 jobs",
      "monitor heartbeat",
      "agent.code-review PR #847",
      "lead.scoring batch 38 leads",
      "trading-bot signal BTC long",
      "voice-clone job done 3.2s",
    ];
    const t = setInterval(() => {
      setLines((l) =>
        [evts[Math.floor(Math.random() * evts.length)], ...l].slice(0, 6),
      );
    }, 2200);
    return () => clearInterval(t);
  }, []);
  return (
    <div className="syslog">
      {lines.map((l, i) => (
        <div
          key={i + l}
          className="syslog-line"
          style={{ opacity: 1 - i * 0.13 }}
        >
          <span className="syslog-dot"></span> {l}
        </div>
      ))}
    </div>
  );
}

// ===== Mount =====
const root = ReactDOM.createRoot(document.getElementById("os-root"));
root.render(<PrzelomOS />);

// ===== Top-strip clock (only visible in 'off' phase) =====
const updTime = () => {
  const d = new Date();
  const el = document.getElementById("strip-time");
  if (el) el.textContent = d.toLocaleTimeString("pl-PL", { hour12: false });
};
updTime();
setInterval(updTime, 1000);
