type CommissionEvent = {
  sessionUID: string
  actionType: string
  actionTime: string
  durationMs: number
  adUserId?: number | null
  mInoutId?: number | null
  cOrderId?: number | null
  mProductId?: number | null
  cBPartnerId?: number | null
  foreignAdOrgId?: number | null
  qty?: number | null
  shippingService?: string | null
  description?: string | null
}

type TrackInput = Partial<Omit<CommissionEvent, 'sessionUID' | 'actionType' | 'actionTime' | 'durationMs'>>

const ENDPOINT = '/api/commission/activity'
const FLUSH_MS = 4000

export const useCommissionActivity = () => {
  const sessionUID = ref<string | null>(null)
  const startedAt = ref<number>(0)
  const queue: CommissionEvent[] = []
  // Bound FK context for the active session — copied onto every track() call
  // so each cust_commissionactivity row carries the same m_inout / c_order /
  // c_bpartner / foreign_ad_org keys and can be joined back to the shipment.
  const sessionContext = ref<TrackInput>({})
  let timer: any = null
  let visibilityHandler: (() => void) | null = null

  const adUserIdCookie = useCookie<string | number | null>('logship_user_id')
  const adUserId = Number(adUserIdCookie.value) || null
  const requestHeaders = useRequestHeaders(['cookie'])

  const flush = () => {
    if (!queue.length) return
    const batch = queue.splice(0, queue.length)
    $fetch(ENDPOINT, {
      method: 'POST',
      headers: requestHeaders,
      body: { events: batch },
    }).catch(() => {})
  }

  const flushBeacon = () => {
    if (!import.meta.client || !queue.length) return
    const batch = queue.splice(0, queue.length)
    try {
      const blob = new Blob([JSON.stringify({ events: batch })], { type: 'application/json' })
      if (!navigator.sendBeacon?.(ENDPOINT, blob)) {
        $fetch(ENDPOINT, {
          method: 'POST',
          headers: requestHeaders,
          body: { events: batch },
          keepalive: true,
        } as any).catch(() => {})
      }
    } catch {}
  }

  const attachVisibility = () => {
    if (!import.meta.client || visibilityHandler) return
    visibilityHandler = () => {
      if (document.visibilityState === 'hidden') flushBeacon()
    }
    window.addEventListener('visibilitychange', visibilityHandler)
  }

  const detachVisibility = () => {
    if (!import.meta.client || !visibilityHandler) return
    window.removeEventListener('visibilitychange', visibilityHandler)
    visibilityHandler = null
  }

  const track = (actionType: string, data: TrackInput = {}) => {
    if (!sessionUID.value) return
    queue.push({
      sessionUID: sessionUID.value,
      actionType,
      actionTime: new Date().toISOString(),
      durationMs: Date.now() - startedAt.value,
      adUserId,
      mInoutId: sessionContext.value.mInoutId ?? null,
      cOrderId: sessionContext.value.cOrderId ?? null,
      cBPartnerId: sessionContext.value.cBPartnerId ?? null,
      foreignAdOrgId: sessionContext.value.foreignAdOrgId ?? null,
      ...data,
    })
  }

  const start = (ctx: TrackInput = {}) => {
    if (sessionUID.value) return
    sessionUID.value = (import.meta.client && crypto?.randomUUID)
      ? crypto.randomUUID()
      : `sess-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`
    startedAt.value = Date.now()
    sessionContext.value = {
      mInoutId: ctx.mInoutId ?? null,
      cOrderId: ctx.cOrderId ?? null,
      cBPartnerId: ctx.cBPartnerId ?? null,
      foreignAdOrgId: ctx.foreignAdOrgId ?? null,
    }
    track('session_start', ctx)
    if (!timer) timer = setInterval(flush, FLUSH_MS)
    attachVisibility()
  }

  const stop = () => {
    if (!sessionUID.value) return
    track('session_complete')
    flushBeacon()
    sessionUID.value = null
    sessionContext.value = {}
    if (timer) { clearInterval(timer); timer = null }
    detachVisibility()
  }

  const abandon = () => {
    if (!sessionUID.value) return
    const sid = sessionUID.value
    queue.length = 0
    sessionUID.value = null
    sessionContext.value = {}
    if (timer) { clearInterval(timer); timer = null }
    detachVisibility()
    // Fire DELETE immediately with keepalive so it survives tab close /
    // navigation. The server runs a second sweep ~3s later to catch any
    // in-flight POST from the last flush that lands after this DELETE.
    $fetch(ENDPOINT, {
      method: 'DELETE',
      headers: requestHeaders,
      body: { sessionUID: sid },
      keepalive: true,
    } as any).catch(() => {})
  }

  if (import.meta.client) {
    onScopeDispose(() => {
      if (sessionUID.value) { flushBeacon() }
      if (timer) { clearInterval(timer); timer = null }
      detachVisibility()
    })
  }

  return {
    start,
    track,
    stop,
    abandon,
    sessionUID: readonly(sessionUID),
  }
}
