import {
  closeSync,
  existsSync,
  mkdirSync,
  openSync,
  readFileSync,
  unlinkSync,
  watch,
  writeFileSync
} from 'node:fs'
import net from 'node:net'
import {
  buildDeeplinkPayload,
  findProtocolUrl,
  forwardDeeplinkToRunningInstance,
  getInstanceDir,
  getLockPath,
  getSignalPath,
  isProcessAlive,
  SIGNAL_FILE,
  WINDOWS_PIPE_NAME,
  writeDeeplinkSignal
} from './deeplink-ipc.js'

let lockFd = null
let pollInterval = null
let fsWatcher = null
let pipeServer = null
let handlers = {
  onDeepLink: null,
  onFocus: null
}
const pendingMessages = []

function tryAcquirePrimaryLock() {
  mkdirSync(getInstanceDir(), { recursive: true })
  const lockPath = getLockPath()

  if (existsSync(lockPath)) {
    try {
      const existingPid = Number.parseInt(readFileSync(lockPath, 'utf8').trim(), 10)

      if (existingPid === process.pid) {
        return true
      }

      if (isProcessAlive(existingPid)) {
        return false
      }

      unlinkSync(lockPath)
    } catch {
      try {
        unlinkSync(lockPath)
      } catch {
        // Ignore cleanup failures.
      }
    }
  }

  try {
    lockFd = openSync(lockPath, 'wx')
    writeFileSync(lockFd, String(process.pid))
    return true
  } catch {
    return false
  }
}

function releasePrimaryLock() {
  if (lockFd !== null) {
    try {
      closeSync(lockFd)
    } catch {
      // Ignore close failures.
    }
    lockFd = null
  }

  try {
    unlinkSync(getLockPath())
  } catch {
    // Ignore cleanup failures.
  }
}

export { findProtocolUrl }

export function captureLaunchUrl() {
  return findProtocolUrl(process.argv)
}

function buildLaunchPayload(launchUrl = captureLaunchUrl()) {
  const commandLine = [...process.argv]
  const url = launchUrl || findProtocolUrl(commandLine)

  return buildDeeplinkPayload(url, commandLine)
}

function readSignalQueue({ clear = true } = {}) {
  const signalPath = getSignalPath()
  if (!existsSync(signalPath)) {
    return []
  }

  try {
    const raw = readFileSync(signalPath, 'utf8')
    const parsed = JSON.parse(raw)

    if (clear) {
      unlinkSync(signalPath)
    }

    if (Array.isArray(parsed)) {
      return parsed
    }

    return parsed ? [parsed] : []
  } catch {
    if (clear) {
      try {
        unlinkSync(signalPath)
      } catch {
        // Ignore cleanup failures.
      }
    }
    return []
  }
}

function writeInstanceSignal(payload) {
  writeDeeplinkSignal(payload)
}

function resolveIncomingMessage(message) {
  if (!message || typeof message !== 'object') {
    return null
  }

  if (message.type === 'deeplink') {
    const url = message.url || findProtocolUrl(message.argv || [])
    return url ? { type: 'deeplink', url } : { type: 'focus' }
  }

  if (message.type === 'focus') {
    return { type: 'focus' }
  }

  return null
}

function dispatchMessage(message) {
  const resolved = resolveIncomingMessage(message)
  if (!resolved) return

  if (process.platform === 'win32') {
    console.log('[deeplink][dispatch]', message)
    console.log('[deeplink][dispatch] resolved:', resolved)
  }

  if (!handlers.onDeepLink && !handlers.onFocus) {
    pendingMessages.push(resolved)
    return
  }

  if (resolved.type === 'deeplink' && resolved.url) {
    handlers.onDeepLink?.(resolved.url)
    return
  }

  if (resolved.type === 'focus') {
    handlers.onFocus?.()
  }
}

function processInstanceSignals() {
  for (const message of readSignalQueue({ clear: true })) {
    dispatchMessage(message)
  }
}

function startInstanceSignalWatcher() {
  if (process.platform === 'darwin' || pollInterval) {
    return
  }

  mkdirSync(getInstanceDir(), { recursive: true })
  processInstanceSignals()

  try {
    fsWatcher = watch(getInstanceDir(), (_event, filename) => {
      if (!filename || filename === SIGNAL_FILE) {
        setTimeout(processInstanceSignals, 25)
      }
    })
  } catch {
    // Polling below covers filesystems without reliable watch support.
  }

  pollInterval = setInterval(processInstanceSignals, 100)
}

function startWindowsPipeServer() {
  if (process.platform !== 'win32' || pipeServer) {
    return
  }

  const server = net.createServer((socket) => {
    let buffer = ''

    socket.on('data', (chunk) => {
      buffer += chunk.toString()
    })

    socket.on('end', () => {
      if (!buffer) return

      try {
        dispatchMessage(JSON.parse(buffer))
      } catch {
        // Ignore malformed payloads.
      }
    })
  })

  server.on('error', (error) => {
    if (error?.code !== 'EADDRINUSE') {
      console.warn('[single-instance] pipe server error:', error)
    }
  })

  server.listen({ path: WINDOWS_PIPE_NAME })
  pipeServer = server
}

export async function ensureSingleInstanceLock({ launchUrl } = {}) {
  if (process.platform === 'darwin') {
    return true
  }

  const payload = buildLaunchPayload(launchUrl)

  if (process.platform === 'win32') {
    if (await forwardDeeplinkToRunningInstance(payload)) {
      console.log('[deeplink] forwarded duplicate launcher instance to running app')
      return false
    }

    if (!tryAcquirePrimaryLock()) {
      if (await forwardDeeplinkToRunningInstance(payload)) {
        return false
      }

      writeInstanceSignal(payload)
      return false
    }

    startWindowsPipeServer()
    startInstanceSignalWatcher()
    return true
  }

  if (tryAcquirePrimaryLock()) {
    startInstanceSignalWatcher()
    return true
  }

  writeInstanceSignal(payload)
  return false
}

export function setSingleInstanceHandlers({ onDeepLink, onFocus }) {
  handlers = { onDeepLink, onFocus }

  while (pendingMessages.length > 0) {
    dispatchMessage(pendingMessages.shift())
  }

  processInstanceSignals()
}

export function closeSingleInstanceServer() {
  if (pipeServer) {
    try {
      pipeServer.close()
    } catch {
      // Ignore close failures.
    }
    pipeServer = null
  }

  if (pollInterval) {
    clearInterval(pollInterval)
    pollInterval = null
  }

  if (fsWatcher) {
    try {
      fsWatcher.close()
    } catch {
      // Ignore close failures.
    }
    fsWatcher = null
  }

  releasePrimaryLock()
}
