const PROJECT_EVENTS_CHANNEL = 'jgjs-project-events'
const PROJECT_DELETED_STORAGE_KEY = 'jgjs-project-event:project-deleted'
const RESET_ALL_STORAGE_KEY = 'jgjs-project-event:reset-all'
const PROJECT_EVENT_SESSION_ID_KEY = 'jgjs-project-event-session-id'

type ProjectDeletedPayload = {
  type: 'project-deleted'
  projectId: string
  sourceSessionId: string
  at: number
}

type ResetAllPayload = {
  type: 'reset-all'
  sourceSessionId: string
  at: number
}

const randomSessionId = () => `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`

const getOrCreateSessionId = () => {
  try {
    const existing = String(window.sessionStorage.getItem(PROJECT_EVENT_SESSION_ID_KEY) || '').trim()
    if (existing) return existing
    const next = randomSessionId()
    window.sessionStorage.setItem(PROJECT_EVENT_SESSION_ID_KEY, next)
    return next
  } catch {
    return randomSessionId()
  }
}

const parseProjectDeletedPayload = (raw: string | null): ProjectDeletedPayload | null => {
  if (!raw) return null
  try {
    const parsed = JSON.parse(raw) as Partial<ProjectDeletedPayload>
    if (!parsed || typeof parsed !== 'object') return null
    if (parsed.type !== 'project-deleted') return null
    if (typeof parsed.projectId !== 'string' || !parsed.projectId.trim()) return null
    if (typeof parsed.sourceSessionId !== 'string' || !parsed.sourceSessionId.trim()) return null
    if (typeof parsed.at !== 'number' || !Number.isFinite(parsed.at)) return null
    return {
      type: 'project-deleted',
      projectId: parsed.projectId.trim(),
      sourceSessionId: parsed.sourceSessionId.trim(),
      at: parsed.at
    }
  } catch {
    return null
  }
}

const parseResetAllPayload = (raw: string | null): ResetAllPayload | null => {
  if (!raw) return null
  try {
    const parsed = JSON.parse(raw) as Partial<ResetAllPayload>
    if (!parsed || typeof parsed !== 'object') return null
    if (parsed.type !== 'reset-all') return null
    if (typeof parsed.sourceSessionId !== 'string' || !parsed.sourceSessionId.trim()) return null
    if (typeof parsed.at !== 'number' || !Number.isFinite(parsed.at)) return null
    return {
      type: 'reset-all',
      sourceSessionId: parsed.sourceSessionId.trim(),
      at: parsed.at
    }
  } catch {
    return null
  }
}

export const emitProjectDeleted = (projectIdRaw: string) => {
  const projectId = String(projectIdRaw || '').trim()
  if (!projectId) return
  const payload: ProjectDeletedPayload = {
    type: 'project-deleted',
    projectId,
    sourceSessionId: getOrCreateSessionId(),
    at: Date.now()
  }
  try {
    if (typeof BroadcastChannel !== 'undefined') {
      const bc = new BroadcastChannel(PROJECT_EVENTS_CHANNEL)
      bc.postMessage(payload)
      bc.close()
    }
  } catch {
    // ignore
  }
  try {
    localStorage.setItem(PROJECT_DELETED_STORAGE_KEY, JSON.stringify(payload))
  } catch {
    // ignore
  }
}

export const listenProjectDeleted = (onDeleted: (projectId: string) => void) => {
  const localSessionId = getOrCreateSessionId()
  let bc: BroadcastChannel | null = null

  const handlePayload = (payload: ProjectDeletedPayload | null) => {
    if (!payload) return
    if (payload.sourceSessionId === localSessionId) return
    onDeleted(payload.projectId)
  }

  const onStorage = (event: StorageEvent) => {
    if (event.key !== PROJECT_DELETED_STORAGE_KEY) return
    handlePayload(parseProjectDeletedPayload(event.newValue))
  }

  if (typeof BroadcastChannel !== 'undefined') {
    bc = new BroadcastChannel(PROJECT_EVENTS_CHANNEL)
    bc.onmessage = (event: MessageEvent<ProjectDeletedPayload>) => {
      handlePayload(parseProjectDeletedPayload(JSON.stringify(event.data)))
    }
  }
  window.addEventListener('storage', onStorage)

  return () => {
    window.removeEventListener('storage', onStorage)
    if (bc) {
      bc.close()
      bc = null
    }
  }
}

export const emitResetAll = () => {
  const payload: ResetAllPayload = {
    type: 'reset-all',
    sourceSessionId: getOrCreateSessionId(),
    at: Date.now()
  }
  try {
    if (typeof BroadcastChannel !== 'undefined') {
      const bc = new BroadcastChannel(PROJECT_EVENTS_CHANNEL)
      bc.postMessage(payload)
      bc.close()
    }
  } catch {
    // ignore
  }
  try {
    localStorage.setItem(RESET_ALL_STORAGE_KEY, JSON.stringify(payload))
  } catch {
    // ignore
  }
}

export const listenResetAll = (onResetAll: () => void) => {
  const localSessionId = getOrCreateSessionId()
  let bc: BroadcastChannel | null = null

  const handlePayload = (payload: ResetAllPayload | null) => {
    if (!payload) return
    if (payload.sourceSessionId === localSessionId) return
    onResetAll()
  }

  const onStorage = (event: StorageEvent) => {
    if (event.key !== RESET_ALL_STORAGE_KEY) return
    handlePayload(parseResetAllPayload(event.newValue))
  }

  if (typeof BroadcastChannel !== 'undefined') {
    bc = new BroadcastChannel(PROJECT_EVENTS_CHANNEL)
    bc.onmessage = (event: MessageEvent<ResetAllPayload>) => {
      handlePayload(parseResetAllPayload(JSON.stringify(event.data)))
    }
  }
  window.addEventListener('storage', onStorage)

  return () => {
    window.removeEventListener('storage', onStorage)
    if (bc) {
      bc.close()
      bc = null
    }
  }
}
