const listeners = new Map()
const pendingMessages = new Map()
const pendingRequests = new Map()
const RPC_REQUEST_TIMEOUT_MS = 30000
const RPC_RESPONSE_CHANNEL = 'rpcResponse'
let rpc = null
let initPromise = null
let initialized = false
let nextRequestId = 0

export function isElectrobunDesktop() {
  return Boolean(
    typeof window !== 'undefined' &&
    window.__electrobunWebviewId &&
    window.__electrobunRpcSocketPort
  )
}

export function isElectrobunBridgeReady() {
  return initialized && Boolean(rpc?.request)
}

function dispatchMessage(channel, data) {
  if (channel === RPC_RESPONSE_CHANNEL) {
    const pendingRequest = pendingRequests.get(data?.id)
    if (!pendingRequest) return

    pendingRequests.delete(data.id)
    clearTimeout(pendingRequest.timeout)

    if (data.success) {
      pendingRequest.resolve(data.result)
    } else {
      pendingRequest.reject(
        new Error(data.error || 'Desktop RPC request failed.')
      )
    }
    return
  }

  const channelListeners = listeners.get(channel)
  if (!channelListeners?.size) {
    if (!pendingMessages.has(channel)) {
      pendingMessages.set(channel, [])
    }
    pendingMessages.get(channel).push(data)
    return
  }

  for (const callback of channelListeners) {
    callback(data)
  }
}

// Bun delivers push messages via executeJavascript for immediate handling.
// WebSocket RPC messages can be deferred by WKWebView during native window
// transitions (e.g. fullscreen) until the next user interaction.
if (typeof window !== 'undefined') {
  window.__farmcontrolDispatchRpcMessage = dispatchMessage
}

async function setupRpc() {
  // electrobun/view captures webview globals at import time — import only after
  // Electrobun preload has set them (Vite dev can evaluate modules earlier).
  const { default: Electrobun, Electroview } = await import('electrobun/view')

  rpc = Electroview.defineRPC({
    maxRequestTime: 30000,
    handlers: {
      requests: {},
      messages: {
        '*': (channel, data) => {
          dispatchMessage(channel, data)
        }
      }
    }
  })

  new Electrobun.Electroview({ rpc })
}

function shouldWaitForElectrobun() {
  if (isElectrobunDesktop()) {
    return true
  }

  if (typeof window === 'undefined') {
    return false
  }

  if (window.__electrobunWebviewId) {
    return true
  }

  const { protocol, hostname, port } = window.location

  if (protocol === 'views:') {
    return true
  }

  // Vite dev server used by electrobun dev:app
  if (hostname === 'localhost' && port === '5780') {
    return true
  }

  return Boolean(
    window.__electrobun ||
    window.__electrobunEventBridge ||
    window.__electrobunInternalBridge
  )
}

export async function initElectrobunBridge() {
  if (initialized) {
    return
  }

  if (!initPromise) {
    initPromise = (async () => {
      if (!shouldWaitForElectrobun()) {
        return
      }

      const deadline = Date.now() + 3000

      while (Date.now() < deadline) {
        if (isElectrobunDesktop()) {
          await setupRpc()
          window.electronAPI = electronAPI
          initialized = true
          return
        }

        await new Promise((resolve) => setTimeout(resolve, 50))
      }

      console.warn(
        'Electrobun bridge: webview globals not found; desktop RPC unavailable.'
      )
    })()
  }

  await initPromise
}

function onMessage(channel, callback) {
  void initElectrobunBridge()

  if (!listeners.has(channel)) {
    listeners.set(channel, new Set())
  }
  listeners.get(channel).add(callback)

  const queued = pendingMessages.get(channel)
  if (queued?.length) {
    pendingMessages.delete(channel)
    for (const payload of queued) {
      callback(payload)
    }
  }

  return () => {
    listeners.get(channel)?.delete(callback)
  }
}

function removeAllListeners(channel) {
  listeners.delete(channel)
  pendingMessages.delete(channel)
}

function invokeNativeRequest(method, params) {
  const nativeBridge = window.__electrobunBunBridge
  if (!nativeBridge?.postMessage) {
    return null
  }

  const id = `${Date.now()}-${++nextRequestId}`

  return new Promise((resolve, reject) => {
    const timeout = setTimeout(() => {
      pendingRequests.delete(id)
      reject(new Error(`Desktop RPC request timed out: ${method}`))
    }, RPC_REQUEST_TIMEOUT_MS)

    pendingRequests.set(id, { resolve, reject, timeout })

    try {
      nativeBridge.postMessage(
        JSON.stringify({
          type: 'message',
          id: 'rendererRequest',
          payload: { id, method, params }
        })
      )
    } catch (error) {
      clearTimeout(timeout)
      pendingRequests.delete(id)
      reject(error)
    }
  })
}

async function invokeRequest(method, params = {}) {
  await initElectrobunBridge()

  if (!rpc?.request) {
    console.warn(`Electrobun RPC unavailable for request: ${method}`)
    return null
  }

  try {
    const nativeRequest = invokeNativeRequest(method, params)
    if (nativeRequest) {
      return await nativeRequest
    }

    return await rpc.request[method](params)
  } catch (error) {
    console.warn(`Electrobun RPC request failed: ${method}`, error)
    return null
  }
}

const electronAPI = {
  get isDesktop() {
    return isElectrobunDesktop()
  },
  onMessage,
  removeAllListeners,
  getOsInfo: () => invokeRequest('getOsInfo'),
  getWindowState: () => invokeRequest('getWindowState'),
  windowControl: (action) => invokeRequest('windowControl', { action }),
  openExternalUrl: (url) => invokeRequest('openExternalUrl', { url }),
  openInternalUrl: (url) => invokeRequest('openInternalUrl', { url }),
  getAuthSession: () => invokeRequest('getAuthSession'),
  setAuthSession: (session) => invokeRequest('setAuthSession', { session }),
  clearAuthSession: () => invokeRequest('clearAuthSession'),
  getAppSettings: () => invokeRequest('getAppSettings'),
  setAppSettings: (settings) => invokeRequest('setAppSettings', { settings }),
  startAppUpdate: (update) => invokeRequest('startAppUpdate', { update }),
  checkAppUpdateResult: () => invokeRequest('checkAppUpdateResult'),
  checkDuplicateInstallations: () =>
    invokeRequest('checkDuplicateInstallations'),
  removeDuplicateInstallations: () =>
    invokeRequest('removeDuplicateInstallations'),
  resizeSpotlightWindow: (height) =>
    invokeRequest('resizeSpotlightWindow', { height }),
  setSidebarViewMenu: (sections) =>
    invokeRequest('setSidebarViewMenu', { sections }),
  getAppVersion: () => invokeRequest('getAppVersion'),
  getAppEngine: () => invokeRequest('getAppEngine')
}

if (typeof window !== 'undefined') {
  window.electronAPI = electronAPI
}

export default electronAPI
