import { RefObject, useLayoutEffect } from 'react';

const STREAM_DEBUG_ENABLE_KEY = 'vojo_stream_debug';
const STREAM_DEBUG_LIMIT = 120;

type StreamDebugRefs = {
  root: RefObject<HTMLElement>;
  timeColumn: RefObject<HTMLElement>;
  rail: RefObject<HTMLElement>;
  dot: RefObject<HTMLElement>;
  content?: RefObject<HTMLElement>;
  header?: RefObject<HTMLElement>;
};

type StreamDebugKind = 'message' | 'sysline';

export type UseStreamLayoutDebug = (
  kind: StreamDebugKind,
  refs: StreamDebugRefs,
  active?: boolean
) => void;

// Vite folds `import.meta.env.DEV` to a literal at build time, so the heavy
// dev-only implementation below is dropped from production bundles via dead-
// code elimination — only the no-op assignment ships. Opt-in at runtime via
// `localStorage.setItem('vojo_stream_debug', '1')`.
const noopUseStreamLayoutDebug: UseStreamLayoutDebug = () => undefined;

let exportedHook: UseStreamLayoutDebug = noopUseStreamLayoutDebug;

if (import.meta.env.DEV) {
  const round = (value: number) => Math.round(value * 100) / 100;
  const centerY = (rect: DOMRect) => rect.top + rect.height / 2;

  // Dump every CSS custom property visible on the element so we can confirm
  // whether vanilla-extract's `vars: {}` overrides are reaching the timestamp
  // element. vanilla-extract hashes var names, so we iterate everything
  // starting with `--` rather than guessing the exact identifier.
  const collectCssVars = (style: CSSStyleDeclaration) => {
    const vars: Record<string, string> = {};
    for (let i = 0; i < style.length; i += 1) {
      const prop = style[i];
      if (prop.startsWith('--')) {
        vars[prop] = style.getPropertyValue(prop).trim();
      }
    }
    return vars;
  };

  const inspectElement = (element: HTMLElement | null) => {
    if (!element) return undefined;
    const rect = element.getBoundingClientRect();
    const style = window.getComputedStyle(element);

    return {
      top: round(rect.top),
      right: round(rect.right),
      bottom: round(rect.bottom),
      left: round(rect.left),
      width: round(rect.width),
      height: round(rect.height),
      centerY: round(centerY(rect)),
      display: style.display,
      paddingTop: style.paddingTop,
      paddingRight: style.paddingRight,
      paddingBottom: style.paddingBottom,
      paddingLeft: style.paddingLeft,
      marginTop: style.marginTop,
      marginRight: style.marginRight,
      marginBottom: style.marginBottom,
      marginLeft: style.marginLeft,
      lineHeight: style.lineHeight,
      fontSize: style.fontSize,
      opacity: style.opacity,
      transform: style.transform,
      cssVars: collectCssVars(style),
    };
  };

  type StreamDebugEntry = {
    label: string;
    kind: StreamDebugKind;
    eventId?: string;
    eventItem?: string;
    timeText: string;
    deltas: {
      timeCenterMinusDotCenter?: number;
      contentTopMinusDotCenter?: number;
      headerCenterMinusDotCenter?: number;
      railTopMinusDotCenter?: number;
      railBottomMinusDotCenter?: number;
    };
    root?: ReturnType<typeof inspectElement>;
    timeColumn?: ReturnType<typeof inspectElement>;
    time?: ReturnType<typeof inspectElement>;
    dot?: ReturnType<typeof inspectElement>;
    rail?: ReturnType<typeof inspectElement>;
    content?: ReturnType<typeof inspectElement>;
    header?: ReturnType<typeof inspectElement>;
    rootOffset?: {
      timeTopFromRoot?: number;
      dotTopFromRoot?: number;
      contentTopFromRoot?: number;
      headerTopFromRoot?: number;
    };
  };

  type DebugWindow = Window & {
    __VOJO_STREAM_DEBUG__?: StreamDebugEntry[];
    vojoStreamDebugDump?: () => StreamDebugEntry[];
  };

  const getEnabled = () =>
    typeof window !== 'undefined' &&
    window.localStorage.getItem(STREAM_DEBUG_ENABLE_KEY) === '1';

  const pushEntry = (entry: StreamDebugEntry) => {
    const w = window as DebugWindow;
    w.__VOJO_STREAM_DEBUG__ = [...(w.__VOJO_STREAM_DEBUG__ ?? []), entry].slice(
      -STREAM_DEBUG_LIMIT
    );
    w.vojoStreamDebugDump = () => w.__VOJO_STREAM_DEBUG__ ?? [];
  };

  const useStreamLayoutDebugDev: UseStreamLayoutDebug = (kind, refs, active = true) => {
    const { root, timeColumn, rail, dot, content, header } = refs;

    useLayoutEffect(() => {
      if (!active) return undefined;
      if (!getEnabled()) return undefined;

      const frame = window.requestAnimationFrame(() => {
        const rootElement = root.current;
        const timeColumnElement = timeColumn.current;
        const time = timeColumnElement?.querySelector('time') as HTMLElement | null;
        const dotElement = dot.current;
        const railElement = rail.current;
        const contentElement = content?.current;
        const headerElement = header?.current;

        const rootRect = rootElement?.getBoundingClientRect();
        const timeRect = time?.getBoundingClientRect();
        const dotRect = dotElement?.getBoundingClientRect();
        const contentRect = contentElement?.getBoundingClientRect();
        const headerRect = headerElement?.getBoundingClientRect();

        const eventId = rootElement
          ?.closest('[data-message-id]')
          ?.getAttribute('data-message-id');
        const eventItem = rootElement
          ?.closest('[data-message-item]')
          ?.getAttribute('data-message-item');
        const timeCenter = timeRect ? centerY(timeRect) : undefined;
        const dotCenter = dotRect ? centerY(dotRect) : undefined;
        const label = `[vojo stream] ${kind} item=${eventItem ?? '?'} event=${eventId ?? '?'} time="${
          time?.textContent?.trim() ?? ''
        }"`;
        const entry: StreamDebugEntry = {
          label,
          kind,
          eventId: eventId ?? undefined,
          eventItem: eventItem ?? undefined,
          timeText: time?.textContent?.trim() ?? '',
          deltas: {
            timeCenterMinusDotCenter:
              timeCenter !== undefined && dotCenter !== undefined
                ? round(timeCenter - dotCenter)
                : undefined,
            contentTopMinusDotCenter:
              contentRect && dotCenter !== undefined
                ? round(contentRect.top - dotCenter)
                : undefined,
            headerCenterMinusDotCenter:
              headerRect && dotCenter !== undefined
                ? round(centerY(headerRect) - dotCenter)
                : undefined,
            railTopMinusDotCenter:
              railElement && dotCenter !== undefined
                ? round(railElement.getBoundingClientRect().top - dotCenter)
                : undefined,
            railBottomMinusDotCenter:
              railElement && dotCenter !== undefined
                ? round(railElement.getBoundingClientRect().bottom - dotCenter)
                : undefined,
          },
          root: inspectElement(rootElement ?? null),
          timeColumn: inspectElement(timeColumnElement ?? null),
          time: inspectElement(time),
          dot: inspectElement(dotElement),
          rail: inspectElement(railElement),
          content: inspectElement(contentElement ?? null),
          header: inspectElement(headerElement ?? null),
          rootOffset: rootRect
            ? {
                timeTopFromRoot: timeRect ? round(timeRect.top - rootRect.top) : undefined,
                dotTopFromRoot: dotRect ? round(dotRect.top - rootRect.top) : undefined,
                contentTopFromRoot: contentRect
                  ? round(contentRect.top - rootRect.top)
                  : undefined,
                headerTopFromRoot: headerRect ? round(headerRect.top - rootRect.top) : undefined,
              }
            : undefined,
        };

        pushEntry(entry);
        // eslint-disable-next-line no-console
        console.groupCollapsed(label);
        // eslint-disable-next-line no-console
        console.log(entry);
        // eslint-disable-next-line no-console
        console.groupEnd();
      });

      return () => window.cancelAnimationFrame(frame);
    }, [active, kind, root, timeColumn, rail, dot, content, header]);
  };

  exportedHook = useStreamLayoutDebugDev;
}

export const useStreamLayoutDebug: UseStreamLayoutDebug = exportedHook;
