/* t-skeleton harness: router shim, stubs shared by every ported file.
 * Everything attaches to window.T. Load order: lib → icons → ui → data →
 * layout → features → app. */
(() => {
  const { useState, useEffect, useCallback, useSyncExternalStore } = React;

  const T = (window.T = window.T || {});

  /* ---------- cn (port of packages/shared/src/lib/cn.ts) ----------
   * The app's cn is `twMerge(clsx(...))`, NOT a string join. Every ported call
   * site relies on tailwind-merge's conflict resolution — the last class in a
   * group wins regardless of where it sits in the compiled stylesheet. A plain
   * join leaves both classes on the element and lets theme.css source order
   * decide, which silently breaks the app's own overrides:
   *   - LogoAvatar size="xs" + className="size-4" → .size-6 (later in
   *     theme.css) beat .size-4, so row logos rendered 24px instead of 16px.
   *   - a Button with `hidden` → the base .inline-flex (later than .hidden)
   *     kept winning, so the sidebar collapse toggle never hid.
   * tailwind-merge 3.5.0 (the app's version) is vendored in vendor/. */
  const twMergeConfig = {
    /* The design foundation ships a role typography vocabulary as custom
     * classes (.text-card-title / .text-meta / …). tailwind-merge doesn't know
     * them, so by default it misclassifies them and drops the role class when a
     * text-{color} is also present. Register them in the `font-size` group —
     * verbatim from the app's cn.ts. */
    extend: {
      classGroups: {
        'font-size': [
          {
            text: [
              'display',
              'hero',
              'page-title',
              'section',
              'card-title',
              'name',
              'body',
              'meta',
              'label',
              'eyebrow',
              'code',
              '2xs', // smallest scale step (avatar/badge micro-chrome)
              'caption', // 11px control caption (table headers, count chips)
              'dense', // 13px compact-table/tab-strip step (no Tailwind default)
              'name-sm', // 13px compact entity-name role (dense tables)
              'body-sm', // 13px compact body role (dense tables)
            ],
          },
        ],
      },
    },
  };

  /* clsx-lite: strings, arrays and {class: bool} objects, same as clsx. */
  const clsx = (...args) => {
    const out = [];
    const walk = (a) => {
      if (!a) return;
      if (typeof a === 'string' || typeof a === 'number') out.push(String(a));
      else if (Array.isArray(a)) a.forEach(walk);
      else if (typeof a === 'object')
        for (const k in a) if (a[k]) out.push(k);
    };
    args.forEach(walk);
    return out.join(' ');
  };

  const twMerge = window.tailwindMerge
    ? window.tailwindMerge.extendTailwindMerge(twMergeConfig)
    : ((s) => s);
  if (!window.tailwindMerge)
    console.error(
      't-skeleton: vendor/tailwind-merge.js failed to load — cn() falls back ' +
        'to a plain join and class conflicts will resolve by stylesheet order.',
    );

  T.cn = (...args) => twMerge(clsx(...args));

  /* ---------- history-based router shim (react-router surface) ---------- */
  const routerListeners = new Set();
  const notify = () => routerListeners.forEach((fn) => fn());
  window.addEventListener('popstate', notify);

  const navigate = (to, opts = {}) => {
    const url =
      typeof to === 'string'
        ? to
        : (to.pathname || location.pathname) + (to.search || '');
    if (opts.replace) history.replaceState(null, '', url);
    else history.pushState(null, '', url);
    notify();
  };

  const subscribe = (fn) => {
    routerListeners.add(fn);
    return () => routerListeners.delete(fn);
  };
  const getPath = () => location.pathname;
  const getSearch = () => location.search;

  const useLocation = () => ({
    pathname: useSyncExternalStore(subscribe, getPath),
    search: useSyncExternalStore(subscribe, getSearch),
  });

  const useNavigate = () => navigate;

  /* Match "/user/home/:view" style patterns. */
  const matchPath = (pattern, pathname) => {
    const p = pattern.split('/').filter(Boolean);
    const a = pathname.split('/').filter(Boolean);
    if (p.length !== a.length) return null;
    const params = {};
    for (let i = 0; i < p.length; i++) {
      if (p[i].startsWith(':')) params[p[i].slice(1)] = decodeURIComponent(a[i]);
      else if (p[i] !== a[i]) return null;
    }
    return params;
  };

  const useParams = () => {
    const { pathname } = useLocation();
    for (const pattern of T.ROUTE_PATTERNS || []) {
      const params = matchPath(pattern, pathname);
      if (params) return params;
    }
    return {};
  };

  const useSearchParams = () => {
    const { search } = useLocation();
    const params = new URLSearchParams(search);
    const setParams = useCallback((next, opts = {}) => {
      const sp =
        typeof next === 'function'
          ? next(new URLSearchParams(location.search))
          : next instanceof URLSearchParams
            ? next
            : new URLSearchParams(next);
      const qs = sp.toString();
      navigate(location.pathname + (qs ? `?${qs}` : ''), {
        replace: opts.replace,
      });
    }, []);
    return [params, setParams];
  };

  const Link = ({ to, children, elementRef, ...rest }) => (
    <a
      href={typeof to === 'string' ? to : to.pathname + (to.search || '')}
      ref={elementRef}
      onClick={(e) => {
        if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0)
          return;
        e.preventDefault();
        navigate(to);
      }}
      {...rest}
    >
      {children}
    </a>
  );

  const Navigate = ({ to, replace }) => {
    useEffect(() => {
      navigate(to, { replace });
    }, []);
    return null;
  };

  T.Router = {
    navigate,
    useNavigate,
    useLocation,
    useParams,
    useSearchParams,
    matchPath,
    Link,
    Navigate,
  };

  /* ---------- theme (shared ThemeProvider surface: .light/.dark on <html>) ---------- */
  const THEME_KEY = 't-skeleton-theme';
  const themeListeners = new Set();
  const getTheme = () => localStorage.getItem(THEME_KEY) || 'dark';
  const applyTheme = (t) => {
    document.documentElement.classList.remove('light', 'dark');
    document.documentElement.classList.add(t);
  };
  applyTheme(getTheme());
  T.useTheme = () => {
    const theme = useSyncExternalStore(
      (fn) => {
        themeListeners.add(fn);
        return () => themeListeners.delete(fn);
      },
      getTheme,
    );
    const setTheme = (t) => {
      localStorage.setItem(THEME_KEY, t);
      applyTheme(t);
      themeListeners.forEach((fn) => fn());
    };
    return { theme, resolvedTheme: theme, setTheme };
  };

  /* ---------- sonner-style toasts ---------- */
  const toasts = [];
  let toastTick = null;
  let toastId = 0;
  const pushToast = (kind, message, opts = {}) => {
    toasts.push({ id: ++toastId, kind, message, ...opts });
    if (toastTick) toastTick();
    setTimeout(() => {
      const i = toasts.findIndex((t) => t.id === toastId);
      if (i >= 0) toasts.splice(i, 1);
      if (toastTick) toastTick();
    }, opts.duration || 4000);
    return toastId;
  };
  const toast = (m, o) => pushToast('default', m, o);
  toast.success = (m, o) => pushToast('success', m, o);
  toast.error = (m, o) => pushToast('error', m, o);
  toast.info = (m, o) => pushToast('info', m, o);
  toast.loading = (m, o) => pushToast('loading', m, o);
  toast.dismiss = (id) => {
    const i = toasts.findIndex((t) => t.id === id);
    if (i >= 0) toasts.splice(i, 1);
    if (toastTick) toastTick();
  };
  T.toast = toast;

  T.Toaster = () => {
    const [, force] = useState(0);
    useEffect(() => {
      toastTick = () => force((n) => n + 1);
      return () => {
        toastTick = null;
      };
    }, []);
    return ReactDOM.createPortal(
      <div
        style={{
          position: 'fixed',
          bottom: 24,
          right: 24,
          zIndex: 9999,
          display: 'flex',
          flexDirection: 'column',
          gap: 8,
        }}
      >
        {toasts.map((t) => (
          <div
            key={t.id}
            className="rounded-lg border border-border bg-popover px-4 py-3 text-sm text-popover-foreground shadow-lg"
            style={{ minWidth: 260, maxWidth: 380 }}
          >
            {t.message}
            {t.description ? (
              <div className="text-muted-foreground mt-1 text-xs">
                {t.description}
              </div>
            ) : null}
          </div>
        ))}
      </div>,
      document.body,
    );
  };

  /* ---------- misc app stubs ---------- */
  T.analytics = new Proxy({}, { get: () => () => {} });
  T.homeAnalytics = T.analytics;
  T.useFeatureFlag = () => true;
  T.useRegisterPageContext = () => {};
  T.Portal = ({ children, container }) =>
    ReactDOM.createPortal(children, container || document.body);

  /* Dismissable-layer hook for menus/popovers/dialog shims. */
  T.useDismiss = (open, onClose, ref) => {
    useEffect(() => {
      if (!open) return;
      const onKey = (e) => {
        if (e.key === 'Escape') onClose();
      };
      const onDown = (e) => {
        if (ref && ref.current && !ref.current.contains(e.target)) onClose();
      };
      document.addEventListener('keydown', onKey);
      document.addEventListener('mousedown', onDown);
      return () => {
        document.removeEventListener('keydown', onKey);
        document.removeEventListener('mousedown', onDown);
      };
    }, [open, onClose]);
  };

  /* Debounce (port of use-debounced-value.ts). */
  T.useDebouncedValue = (value, delay = 250) => {
    const [v, setV] = useState(value);
    useEffect(() => {
      const id = setTimeout(() => setV(value), delay);
      return () => clearTimeout(id);
    }, [value, delay]);
    return v;
  };
})();
