import { ref, onMounted, onUnmounted, readonly } from 'vue'
import { io, Socket } from 'socket.io-client'

// Types
export interface RealtimeEvents {
  // Document events
  pg_update: (data: PgUpdateData) => void
  pg_viewers: (data: PgViewersData) => void
  docinfo_update: (data: DocInfoUpdateData) => void
  list_update: (data: ListUpdateData) => void

  // Task events
  task_status_change: (data: TaskStatusData) => void
  task_progress: (data: TaskProgressData) => void

  // Notification events
  notification: () => void
  msgprint: (message: string) => void
  progress: (data: ProgressData) => void

  // System events
  'version-update': () => void
  energy_point_alert: (message: any) => void
}

export interface PgUpdateData {
  pagetype: string
  name: string
  modified: string
  user?: string
}

export interface PgViewersData {
  pagetype: string
  docname: string
  users: string[]
}

export interface DocInfoUpdateData {
  pg: string
  key: string
  action?: 'update' | 'delete'
}

export interface ListUpdateData {
  pagetype: string
  name: string
  action?: string
}

export interface TaskStatusData {
  task_id: string
  status: string
  status_code?: number
  message?: string
}

export interface TaskProgressData {
  task_id: string
  progress?: [number, number]
  percent?: number
  title?: string
  description?: string
}

export interface ProgressData {
  progress?: [number, number]
  percent?: number
  title?: string
  description?: string
}

export interface TaskOptions {
  success?: (data: any) => void
  error?: (data: any) => void
  progress?: (data: any) => void
  callback?: (data: any) => void
  always?: (data: any) => void
}

// Global socket instance (singleton)
let socketInstance: Socket | null = null
let isInitialized = false
let lazyConnect = false

// Global state
const connected = ref(false)
const connecting = ref(false)
const connectionError = ref<string | null>(null)

// Track open tasks and documents
const openTasks = new Map<string, TaskOptions>()
const openDocs = new Set<string>()

// Event listeners registry
const eventListeners = new Map<string, Set<Function>>()

// Throttle flag for pg_subscribe
let pgSubscribeThrottle = false

/**
 * Get socket.io server URL
 */
function getSocketUrl(): string {
  const boot = (window as any).jingrow?.boot || {}
  const isDevServer = !!boot.dev_server
  const socketPort = boot.socketio_port || '9000'
  const siteName = boot.sitename

  if (!siteName) {
    return ''
  }

  if (isDevServer) {
    const host = window.location.hostname
    const protocol = window.location.protocol === 'https:' ? 'https' : 'http'
    return `${protocol}://${host}:${socketPort}/${siteName}`
  }

  // Production: use same origin with site namespace
  return `${window.location.origin}/${siteName}`
}

/**
 * Initialize socket connection (lazy by default)
 */
function initSocket(options: UseRealtimeOptions = {}): Socket | null {
  if (socketInstance) return socketInstance

  const boot = (window as any).jingrow?.boot
  if (boot?.disable_async) return null

  lazyConnect = options.lazyConnect ?? true

  try {
    const url = getSocketUrl()
    if (!url) {
      return null
    }
    
    const isHttps = window.location.protocol === 'https:'

    socketInstance = io(url, {
      secure: isHttps,
      withCredentials: true,
      reconnectionAttempts: options.reconnectionAttempts ?? 5,
      reconnectionDelay: 1000,
      reconnectionDelayMax: 5000,
      autoConnect: !lazyConnect,
      transports: ['websocket', 'polling'],
    })

    setupCoreListeners()
    isInitialized = true
  } catch (error) {
    console.error('[Realtime] Failed to initialize socket:', error)
    connectionError.value = 'Failed to initialize socket connection'
    return null
  }

  return socketInstance
}

/**
 * Setup core event listeners
 */
function setupCoreListeners() {
  if (!socketInstance) return

  socketInstance.on('connect', () => {
    connected.value = true
    connecting.value = false
    connectionError.value = null
  })

  socketInstance.on('disconnect', (reason) => {
    connected.value = false
    if (reason === 'io server disconnect') {
      socketInstance?.connect()
    }
  })

  socketInstance.on('connect_error', (error) => {
    connecting.value = false
    connectionError.value = error.message
  })

  socketInstance.on('msgprint', (message: string) => {
    emitToListeners('msgprint', message)
  })

  socketInstance.on('progress', (data: ProgressData) => {
    if (data.progress) {
      data.percent = (data.progress[0] / data.progress[1]) * 100
    }
    emitToListeners('progress', data)
  })

  socketInstance.on('task_status_change', (data: TaskStatusData) => {
    processTaskResponse(data, data.status.toLowerCase())
  })

  socketInstance.on('task_progress', (data: TaskProgressData) => {
    processTaskResponse(data, 'progress')
  })
}

/**
 * Emit event to registered listeners
 */
function emitToListeners(event: string, data: any) {
  const listeners = eventListeners.get(event)
  if (listeners) {
    listeners.forEach((callback) => callback(data))
  }
}

/**
 * Process task response
 */
function processTaskResponse(data: any, method: string) {
  if (!data?.task_id) return

  const opts = openTasks.get(data.task_id)
  if (!opts) return

  // Call specific handler
  if ((opts as any)[method]) {
    ;(opts as any)[method](data)
  }

  // Standard callback for success
  if (method === 'success' && opts.callback) {
    opts.callback(data)
  }

  // Always handler
  if (opts.always) {
    opts.always(data)
  }

  // Error handling
  if (data.status_code && data.status_code > 400 && opts.error) {
    opts.error(data)
  }
}

/**
 * Connect socket if not connected
 */
function ensureConnected() {
  if (!socketInstance) {
    initSocket({ lazyConnect: false })
  } else if (lazyConnect && !connected.value) {
    socketInstance.connect()
  }
}

/**
 * Subscribe to an event
 */
function on<T = any>(event: string, callback: (data: T) => void): () => void {
  ensureConnected()

  // Register listener
  if (!eventListeners.has(event)) {
    eventListeners.set(event, new Set())
  }
  eventListeners.get(event)!.add(callback)

  // Also subscribe to socket if connected
  if (socketInstance) {
    socketInstance.on(event, callback)
  }

  // Return unsubscribe function
  return () => off(event, callback)
}

/**
 * Unsubscribe from an event
 */
function off(event: string, callback?: Function) {
  const listeners = eventListeners.get(event)
  if (listeners) {
    if (callback) {
      listeners.delete(callback)
    } else {
      listeners.clear()
    }
  }

  if (socketInstance && callback) {
    socketInstance.off(event, callback as any)
  }
}

/**
 * Emit an event
 */
function emit(event: string, ...args: any[]) {
  ensureConnected()
  if (socketInstance) {
    socketInstance.emit(event, ...args)
  }
}

// ============== Document Subscriptions ==============

/**
 * Subscribe to document updates
 */
function pgSubscribe(pagetype: string, docname: string) {
  const key = `${pagetype}:${docname}`

  // Throttle check
  if (pgSubscribeThrottle) return

  // Already subscribed
  if (openDocs.has(key)) return

  // Set throttle
  pgSubscribeThrottle = true
  setTimeout(() => {
    pgSubscribeThrottle = false
  }, 1000)

  emit('pg_subscribe', pagetype, docname)
  openDocs.add(key)
}

/**
 * Unsubscribe from document updates
 */
function pgUnsubscribe(pagetype: string, docname: string) {
  const key = `${pagetype}:${docname}`
  emit('pg_unsubscribe', pagetype, docname)
  return openDocs.delete(key)
}

/**
 * Notify that document is opened (for viewers tracking)
 */
function pgOpen(pagetype: string, docname: string) {
  emit('pg_open', pagetype, docname)
}

/**
 * Notify that document is closed
 */
function pgClose(pagetype: string, docname: string) {
  emit('pg_close', pagetype, docname)
}

// ============== Task Subscriptions ==============

/**
 * Subscribe to task updates with full options
 */
function taskSubscribe(taskId: string, options: TaskOptions = {}) {
  emit('task_subscribe', taskId)
  emit('progress_subscribe', taskId)
  openTasks.set(taskId, options)
}

/**
 * Unsubscribe from task updates
 */
function taskUnsubscribe(taskId: string) {
  emit('task_unsubscribe', taskId)
  openTasks.delete(taskId)
}

// ============== PageType Subscriptions ==============

/**
 * Subscribe to pagetype events
 */
function pagetypeSubscribe(pagetype: string) {
  emit('pagetype_subscribe', pagetype)
}

/**
 * Unsubscribe from pagetype events
 */
function pagetypeUnsubscribe(pagetype: string) {
  emit('pagetype_unsubscribe', pagetype)
}

// ============== Utility Methods ==============

/**
 * Ping server to check connection
 */
function ping(): Promise<void> {
  return new Promise((resolve) => {
    if (!socketInstance) {
      resolve()
      return
    }

    const handler = () => {
      resolve()
      socketInstance?.off('pong', handler)
    }
    socketInstance.on('pong', handler)
    socketInstance.emit('ping')

    // Timeout after 5 seconds
    setTimeout(resolve, 5000)
  })
}

/**
 * Publish event to other clients
 */
function publish(event: string, message: any) {
  if (socketInstance && connected.value) {
    emit(event, message)
  }
}

/**
 * Disconnect socket
 */
function disconnect() {
  if (socketInstance) {
    socketInstance.disconnect()
    connected.value = false
  }
}

/**
 * Reconnect socket
 */
function reconnect() {
  if (socketInstance) {
    connecting.value = true
    socketInstance.connect()
  }
}

// ============== Composable Options ==============

export interface UseRealtimeOptions {
  lazyConnect?: boolean
  reconnectionAttempts?: number
}

/**
 * useRealtime composable
 * Provides reactive real-time communication with socket.io
 */
export function useRealtime(options: UseRealtimeOptions = {}) {
  // Initialize on first use
  if (!isInitialized) {
    initSocket(options)
  }

  // Lifecycle cleanup helper
  const cleanupFns: Array<() => void> = []

  /**
   * Subscribe to an event with automatic cleanup
   */
  function subscribe<T = any>(event: string, callback: (data: T) => void) {
    const unsubscribe = on(event, callback)
    cleanupFns.push(unsubscribe)
    return unsubscribe
  }

  /**
   * Subscribe to document updates with automatic cleanup
   */
  function watchDocument(pagetype: string, docname: string, callbacks: {
    onUpdate?: (data: PgUpdateData) => void
    onViewers?: (data: PgViewersData) => void
  }) {
    pgSubscribe(pagetype, docname)
    pgOpen(pagetype, docname)

    if (callbacks.onUpdate) {
      subscribe('pg_update', callbacks.onUpdate)
    }
    if (callbacks.onViewers) {
      subscribe('pg_viewers', callbacks.onViewers)
    }

    return () => {
      pgUnsubscribe(pagetype, docname)
      pgClose(pagetype, docname)
    }
  }

  /**
   * Subscribe to task progress with automatic cleanup
   */
  function watchTask(taskId: string, options: TaskOptions = {}) {
    taskSubscribe(taskId, options)

    return () => {
      taskUnsubscribe(taskId)
    }
  }

  /**
   * Subscribe to list updates with automatic cleanup
   */
  function watchList(pagetype: string, callback: (data: ListUpdateData) => void) {
    pagetypeSubscribe(pagetype)
    return subscribe('list_update', callback)
  }

  // Cleanup on unmount
  onUnmounted(() => {
    cleanupFns.forEach((fn) => fn())
  })

  return {
    // State
    connected: readonly(connected),
    connecting: readonly(connecting),
    connectionError: readonly(connectionError),

    // Core methods
    on,
    off,
    emit,
    subscribe,
    ping,
    publish,

    // Document subscriptions
    pgSubscribe,
    pgUnsubscribe,
    pgOpen,
    pgClose,
    watchDocument,

    // Task subscriptions
    taskSubscribe,
    taskUnsubscribe,
    watchTask,

    // PageType subscriptions
    pagetypeSubscribe,
    pagetypeUnsubscribe,
    watchList,

    // Connection management
    disconnect,
    reconnect,
    ensureConnected,
  }
}

// ============== Global Singleton Access ==============

/**
 * Get global realtime client (for use outside of components)
 */
export function getRealtimeClient() {
  if (!isInitialized) {
    initSocket({ lazyConnect: true })
  }
  return {
    connected,
    connecting,
    connectionError,
    on,
    off,
    emit,
    pgSubscribe,
    pgUnsubscribe,
    pgOpen,
    pgClose,
    taskSubscribe,
    taskUnsubscribe,
    pagetypeSubscribe,
    pagetypeUnsubscribe,
    ping,
    publish,
    disconnect,
    reconnect,
  }
}

// Export types
export type RealtimeClient = ReturnType<typeof getRealtimeClient>
