import { RefObject, useEffect, useRef, useState } from 'react';
import { Room } from 'matrix-js-sdk';
import { useTranslation } from 'react-i18next';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { Theme, useTheme } from '../../hooks/useTheme';
import type { MatrixToRoom } from '../../plugins/matrix-to';
import { isWidgetExperience, type BotPreset } from './catalog';
import { BotWidgetEmbed } from './BotWidgetEmbed';

type UseBotWidgetEmbedOptions = {
  containerRef: RefObject<HTMLElement>;
  preset: BotPreset;
  room: Room;
  onError: () => void;
  // Forwarded into the embed. Plumbed from `BotWidgetMount` where the
  // react-router context is available — the hook stays unaware of routing.
  onOpenMatrixToRoom?: (target: MatrixToRoom) => void;
  // Forwarded into the embed. Fires when the widget posts the elevated
  // `add-to-chat` verb (host opens its own room picker). Plumbed from
  // `BotWidgetMount` where `mx` is available.
  onAddToChat?: () => void;
};

type UseBotWidgetEmbedResult = {
  // True once the widget's `ready` event has fired (capability handshake
  // completed). Resets to false whenever the embed is recreated (room or
  // preset change). Drives the loading-bar overlay in BotWidgetMount.
  ready: boolean;
};

// Encapsulates the imperative BotWidgetEmbed lifecycle so the host React
// component stays small. Mirrors useCallEmbed's split: the hook owns the
// effect, the component owns the DOM ref + visual chrome. Live embed
// instance is held in a private ref for runtime updates (theme
// propagation below); not exposed because no caller needs it today.
export const useBotWidgetEmbed = ({
  containerRef,
  preset,
  room,
  onError,
  onOpenMatrixToRoom,
  onAddToChat,
}: UseBotWidgetEmbedOptions): UseBotWidgetEmbedResult => {
  const { i18n } = useTranslation();
  const mx = useMatrixClient();
  const theme = useTheme();
  const embedRef = useRef<BotWidgetEmbed>();
  const [ready, setReady] = useState(false);

  // Capture mount-time values in refs so the lifecycle effect doesn't re-run
  // when locale/theme change — those are propagated separately via setTheme.
  const themeRef = useRef<Theme>(theme);
  themeRef.current = theme;
  const languageRef = useRef<string>(i18n.language);
  languageRef.current = i18n.language;
  // Same indirection for `onOpenMatrixToRoom`: the callback identity
  // typically changes per render (closes over `navigate`/`mx`), and we do
  // NOT want that to remount the embed. The ref carries the latest fn; the
  // embed only sees a stable shim that re-reads it.
  const onOpenMatrixToRoomRef = useRef(onOpenMatrixToRoom);
  onOpenMatrixToRoomRef.current = onOpenMatrixToRoom;
  // Same ref indirection for `onAddToChat` (closes over `mx`/picker state per
  // render) so the embed lifecycle effect doesn't remount the iframe.
  const onAddToChatRef = useRef(onAddToChat);
  onAddToChatRef.current = onAddToChat;

  // Depend on primitive identity for the embed lifecycle — using `preset`
  // directly would remount the iframe (and re-handshake with the widget)
  // every time `useBotPresets` returns a fresh memo, e.g. after a runtime
  // config refresh.
  const { id: presetId, mxid: presetMxid, name: presetName, experience } = preset;
  const experienceUrl = isWidgetExperience(experience) ? experience.url : undefined;
  const experienceType = experience?.type;
  const { roomId } = room;

  useEffect(() => {
    const container = containerRef.current;
    if (!container) return undefined;

    // Reset readiness whenever the embed is (re)created. Subsequent
    // `ready` event from the new ClientWidgetApi flips this back to true.
    setReady(false);

    let embed: BotWidgetEmbed | undefined;
    try {
      embed = new BotWidgetEmbed({
        mx,
        room,
        preset,
        container,
        theme: themeRef.current,
        language: languageRef.current,
        onError,
        onReady: () => setReady(true),
        // Indirection so the embed lifecycle doesn't reset when the
        // navigate-callback closes over a new render's `mx`/`navigate`.
        onOpenMatrixToRoom: (target) => onOpenMatrixToRoomRef.current?.(target),
        onAddToChat: () => onAddToChatRef.current?.(),
      });
      embedRef.current = embed;
    } catch (error) {
      // eslint-disable-next-line no-console
      console.error('[BotWidget] constructor threw', error);
      onError();
    }

    return () => {
      embed?.destroy();
      if (embedRef.current === embed) {
        embedRef.current = undefined;
      }
    };
    // theme/language read above are intentionally not in deps — they are
    // captured via refs and propagated by the separate effect below.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [
    mx,
    containerRef,
    roomId,
    presetId,
    presetMxid,
    presetName,
    experienceUrl,
    experienceType,
    onError,
  ]);

  // Runtime theme propagation — embed.setTheme is a no-op until the widget
  // handshake completes, so calling it on every theme tick is safe.
  useEffect(() => {
    embedRef.current?.setTheme(theme).catch(() => undefined);
  }, [theme]);

  return { ready };
};
