interface RTEInstance {
  ctx: HTMLTextAreaElement
  loaded: boolean
  view: {
    dom: HTMLElement
  }
}

interface RTETextarea extends HTMLTextAreaElement {
  rte: RTEInstance
}

export interface OnRTEReadyOptions {
  /** If true, auto-unsubscribe after first callback execution. Defaults to true. */
  once?: boolean
}

interface ExecuteContext {
  callback: (rte: RTEInstance) => void
  once: boolean
  hasExecuted: boolean
  subscription: { remove: () => void } | null
}

/**
 * Check if a textarea has an RTE instance
 */
function hasRTE(textarea: HTMLTextAreaElement | null): textarea is RTETextarea {
  return (
    textarea !== null && 'rte' in textarea && typeof textarea.rte === 'object'
  )
}

/**
 * Execute the RTE ready callback with context
 * @param rte - The RTE instance that has loaded
 * @param context - Execution context containing callback, once flag, and state
 */
function executeCallback(rte: RTEInstance, context: ExecuteContext): void {
  if (context.once && context.hasExecuted) {
    return
  }

  try {
    context.callback(rte)
    context.hasExecuted = true
    if (context.once && context.subscription) {
      context.subscription.remove()
      context.subscription = null
    }
  } catch (error) {
    console.error('onRTEReady callback failed', error)
  }
}

/**
 * Execute callback when a specific RTE is fully loaded
 * @param callback - Function called with RTE instance when ready
 * @param target - Textarea element or selector for the specific RTE
 * @param options - Optional configuration
 * @param options.once - Auto-cleanup after first fire (default: true)
 * @returns Cleanup function to unsubscribe
 */
export default function onRTEReady(
  callback: (rte: RTEInstance) => void,
  target: HTMLTextAreaElement | string,
  options?: OnRTEReadyOptions,
): () => void {
  const { once = true } = options || {}

  // Resolve target to textarea element
  const targetTextarea: HTMLTextAreaElement | null =
    typeof target === 'string' ? document.querySelector(target) : target

  if (!(targetTextarea instanceof HTMLTextAreaElement)) {
    return () => {} // No-op
  }

  // Context shared by all executeCallback invocations
  const context: ExecuteContext = {
    callback,
    once,
    hasExecuted: false,
    subscription: null,
  }

  // Already loaded?
  if (hasRTE(targetTextarea) && targetTextarea.rte.loaded) {
    executeCallback(targetTextarea.rte, context)
    if (once) {
      return () => {} // Already executed, no cleanup needed
    }
  }

  context.subscription = window.PubSub.subscribe(
    'rteLoaded',
    (rte: RTEInstance) => {
      if (rte.ctx === targetTextarea) {
        executeCallback(rte, context)
      }
    },
  )

  return () => context.subscription?.remove()
}
