export function measureDocumentHeight(doc: Document, minHeight: number): number {
  const body = doc.body;
  const root = doc.documentElement;
  let measured = Math.ceil(
    Math.max(
      body?.scrollHeight ?? 0,
      body?.offsetHeight ?? 0,
      root?.scrollHeight ?? 0,
      root?.offsetHeight ?? 0,
      minHeight,
    ),
  );

  if (body && measured <= minHeight + 40) {
    const blocks = body.querySelectorAll('.hero, .section, .quote-section, .footer, main, article');
    for (const block of blocks) {
      const el = block as HTMLElement;
      measured = Math.max(measured, el.offsetTop + el.offsetHeight);
    }
    const last = body.lastElementChild as HTMLElement | null;
    if (last) {
      measured = Math.max(measured, last.offsetTop + last.offsetHeight);
    }
  }

  return measured;
}

export function clampIframeHeight(height: number, minHeight: number, maxHeight: number): number {
  return Math.min(Math.max(height, minHeight), maxHeight);
}

export function bindIframeDocumentHeightSync(
  iframe: HTMLIFrameElement,
  {
    minHeight,
    maxHeight,
    onHeight,
  }: {
    minHeight: number;
    maxHeight: number;
    onHeight: (height: number) => void;
  },
): () => void {
  const disposers: Array<() => void> = [];

  const apply = () => {
    try {
      const doc = iframe.contentDocument;
      if (!doc) return;
      onHeight(clampIframeHeight(measureDocumentHeight(doc, minHeight), minHeight, maxHeight));
    } catch {
      onHeight(minHeight);
    }
  };

  const observeDocument = () => {
    const doc = iframe.contentDocument;
    if (!doc?.body) return;

    apply();

    if (typeof ResizeObserver !== 'undefined') {
      const observer = new ResizeObserver(apply);
      observer.observe(doc.body);
      if (doc.documentElement && doc.documentElement !== doc.body) {
        observer.observe(doc.documentElement);
      }
      disposers.push(() => observer.disconnect());
    }

    doc.querySelectorAll('img').forEach((img) => {
      if (img.complete) return;
      const handle = () => apply();
      img.addEventListener('load', handle);
      img.addEventListener('error', handle);
      disposers.push(() => {
        img.removeEventListener('load', handle);
        img.removeEventListener('error', handle);
      });
    });

    for (const delay of [100, 400, 1200]) {
      const timer = window.setTimeout(apply, delay);
      disposers.push(() => window.clearTimeout(timer));
    }
  };

  const onLoad = () => observeDocument();
  iframe.addEventListener('load', onLoad);
  disposers.push(() => iframe.removeEventListener('load', onLoad));

  try {
    if (iframe.contentDocument?.readyState === 'complete') {
      observeDocument();
    }
  } catch {
    // ignore cross-origin access errors
  }

  return () => {
    disposers.forEach((dispose) => dispose());
  };
}
