import browser from 'webextension-polyfill'
import { isLikelyDownloadUrl, normalizeUrl } from '../lib/downloadIntent'
import { getSettings } from '../lib/settings'
import { createAnchorFromSelection, resolveAnchorToRange } from '../lib/clipAnchor'
import { normalizePageUrl, type ClipItem } from '../lib/clipTypes'

const CAPTURE_TTL_MS = 8000
const captureInFlight = new Map<string, number>()
let extensionEnabled = false
let clipRouteWatcherTimer: number | null = null
let lastClipPageUrl = normalizePageUrl(window.location.href)
let clipSyncRunId = 0

function clipLog(...args: unknown[]): void {
  console.log('[gomdown-helper][clip][content]', ...args)
}

function pruneCaptureInFlight(): void {
  const now = Date.now()
  for (const [url, expiresAt] of captureInFlight.entries()) {
    if (expiresAt <= now) captureInFlight.delete(url)
  }
}

async function sendCapture(url: string, referer: string): Promise<boolean> {
  if (!extensionEnabled) return false
  const normalized = normalizeUrl(url, window.location.href)
  if (!normalized) return false

  pruneCaptureInFlight()
  if (captureInFlight.has(normalized)) return true
  captureInFlight.set(normalized, Date.now() + CAPTURE_TTL_MS)

  try {
    const result = (await browser.runtime.sendMessage({
      type: 'capture-link-download',
      url: normalized,
      referer: referer || document.referrer || window.location.href,
    })) as { ok?: boolean }
    if (result?.ok) return true
  } catch {
    // ignored
  }

  captureInFlight.delete(normalized)
  return false
}

function findAnchor(target: EventTarget | null): HTMLAnchorElement | null {
  if (!target) return null
  if (target instanceof HTMLAnchorElement) return target
  if (target instanceof Element) return target.closest('a[href]') as HTMLAnchorElement | null
  return null
}

function shouldIgnoreHotkey(event: MouseEvent | KeyboardEvent): boolean {
  return !!(event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
}

function isEditableEventTarget(target: EventTarget | null): boolean {
  if (!target) return false
  const element = target instanceof Element ? target : target instanceof Node ? target.parentElement : null
  if (!element) return false
  if (element.closest('input, textarea, select')) return true
  if (element.closest('[contenteditable="true"]')) return true
  return false
}

function isClipHotkey(event: KeyboardEvent): boolean {
  const code = String(event.code || '')
  const altShiftC = event.altKey && event.shiftKey && !event.ctrlKey && !event.metaKey && code === 'KeyC'
  const altC = event.altKey && !event.shiftKey && !event.ctrlKey && !event.metaKey && code === 'KeyC'
  const ctrlShiftY = event.ctrlKey && event.shiftKey && !event.altKey && !event.metaKey && code === 'KeyY'
  const metaShiftY = event.metaKey && event.shiftKey && !event.altKey && !event.ctrlKey && code === 'KeyY'
  return altShiftC || altC || ctrlShiftY || metaShiftY
}

function selectionToHtml(selection: Selection): string {
  try {
    if (!selection.rangeCount) return ''
    const range = selection.getRangeAt(0)
    const fragment = range.cloneContents()
    const div = document.createElement('div')
    div.appendChild(fragment)
    return div.innerHTML.trim()
  } catch {
    return ''
  }
}

async function interceptAnchorEvent(event: MouseEvent): Promise<void> {
  if (!extensionEnabled) return
  if (event.defaultPrevented) return
  if (shouldIgnoreHotkey(event)) return

  const anchor = findAnchor(event.target)
  if (!anchor) return
  const href = anchor.href || ''
  if (!href || !isLikelyDownloadUrl(href, window.location.href)) return

  event.preventDefault()
  event.stopImmediatePropagation()
  event.stopPropagation()
  await sendCapture(href, document.referrer || window.location.href)
}

function interceptMouseLike(event: MouseEvent): void {
  if (!extensionEnabled) return
  const anchor = findAnchor(event.target)
  if (!anchor) return
  const href = anchor.href || ''
  if (!href || !isLikelyDownloadUrl(href, window.location.href)) return
  if (shouldIgnoreHotkey(event)) return

  event.preventDefault()
  event.stopImmediatePropagation()
  event.stopPropagation()
  void sendCapture(href, document.referrer || window.location.href)
}

document.addEventListener('pointerdown', (event: PointerEvent) => {
  if (event.button !== 0) return
  interceptMouseLike(event)
}, true)

document.addEventListener('mousedown', (event: MouseEvent) => {
  if (event.button !== 0) return
  interceptMouseLike(event)
}, true)

document.addEventListener('click', (event: MouseEvent) => {
  if (event.button !== 0) return
  void interceptAnchorEvent(event)
}, true)

document.addEventListener('keydown', (event: KeyboardEvent) => {
  if (!extensionEnabled) return
  if (event.key !== 'Enter') return
  if (event.defaultPrevented) return
  if (shouldIgnoreHotkey(event)) return

  const anchor = findAnchor(event.target)
  if (!anchor) return
  const href = anchor.href || ''
  if (!href || !isLikelyDownloadUrl(href, window.location.href)) return

  event.preventDefault()
  event.stopImmediatePropagation()
  event.stopPropagation()
  void sendCapture(href, document.referrer || window.location.href)
}, true)

document.addEventListener('keydown', (event: KeyboardEvent) => {
  if (!isClipHotkey(event)) return
  clipLog('hotkey detected', {
    code: event.code,
    key: event.key,
    alt: event.altKey,
    shift: event.shiftKey,
    ctrl: event.ctrlKey,
    meta: event.metaKey,
    defaultPrevented: event.defaultPrevented,
    href: window.location.href,
  })
  if (isEditableEventTarget(event.target)) return
  if (!extensionEnabled) {
    showClipToast('확장이 OFF 상태입니다.', 'error')
    clipLog('hotkey blocked: extension disabled')
    return
  }

  event.preventDefault()
  event.stopImmediatePropagation()
  event.stopPropagation()
  void createClipFromCurrentSelection()
}, true)

document.addEventListener('auxclick', (event: MouseEvent) => {
  if (event.button !== 1) return
  void interceptAnchorEvent(event)
}, true)

function installProgrammaticInterceptors(): void {
  try {
    const originalOpen = window.open.bind(window)
    window.open = function gomdownInterceptOpen(url?: string | URL, target?: string, features?: string): Window | null {
      const raw = String(url || '').trim()
      if (extensionEnabled && raw && isLikelyDownloadUrl(raw, window.location.href)) {
        void sendCapture(raw, window.location.href)
        return null
      }
      return originalOpen(url as string, target, features)
    }
  } catch {
    // ignored
  }

  try {
    const originalAnchorClick = HTMLAnchorElement.prototype.click
    HTMLAnchorElement.prototype.click = function gomdownInterceptAnchorClick(): void {
      const href = this.href || this.getAttribute('href') || ''
      if (extensionEnabled && href && isLikelyDownloadUrl(href, window.location.href)) {
        void sendCapture(href, document.referrer || window.location.href)
        return
      }
      originalAnchorClick.call(this)
    }
  } catch {
    // ignored
  }
}

installProgrammaticInterceptors()

let ytOverlayRoot: HTMLDivElement | null = null
let ytOverlayStatus: HTMLDivElement | null = null
let ytOverlayBusy = false
let ytUrlWatcherTimer: number | null = null
let lastObservedUrl = window.location.href
let quickPanelRoot: HTMLDivElement | null = null
let quickPanelStatus: HTMLDivElement | null = null
let quickActionBarRoot: HTMLDivElement | null = null
let quickActionBarTimer: number | null = null

function isYoutubeWatchPage(url: string): boolean {
  try {
    const parsed = new URL(url)
    if (parsed.hostname !== 'www.youtube.com' && parsed.hostname !== 'youtube.com') return false
    return parsed.pathname === '/watch' && parsed.searchParams.has('v')
  } catch {
    return false
  }
}

function setYtOverlayStatus(message: string, tone: 'ok' | 'error' | 'idle' = 'idle'): void {
  if (!ytOverlayStatus) return
  ytOverlayStatus.textContent = message
  if (tone === 'ok') ytOverlayStatus.style.color = '#8ff0a4'
  else if (tone === 'error') ytOverlayStatus.style.color = '#ff9b9b'
  else ytOverlayStatus.style.color = '#aeb7d8'
}

async function enqueueCurrentYoutubePage(): Promise<void> {
  if (ytOverlayBusy) return
  ytOverlayBusy = true
  setYtOverlayStatus('gdown으로 전송 중...')
  try {
    const result = (await browser.runtime.sendMessage({
      type: 'page:enqueue-ytdlp-url',
      url: window.location.href,
      referer: window.location.href,
    })) as { ok?: boolean; error?: string }
    if (result?.ok) {
      setYtOverlayStatus('다운로드 모달로 전송됨', 'ok')
    } else {
      setYtOverlayStatus(`전송 실패: ${result?.error || 'unknown error'}`, 'error')
    }
  } catch (error) {
    setYtOverlayStatus(`전송 실패: ${String(error)}`, 'error')
  } finally {
    ytOverlayBusy = false
  }
}

function removeYoutubeOverlay(): void {
  if (ytOverlayRoot) {
    ytOverlayRoot.remove()
    ytOverlayRoot = null
    ytOverlayStatus = null
  }
}

function ensureYoutubeOverlay(): void {
  if (!extensionEnabled) {
    removeYoutubeOverlay()
    return
  }
  if (window.top !== window.self) return
  if (!isYoutubeWatchPage(window.location.href)) {
    removeYoutubeOverlay()
    return
  }
  if (ytOverlayRoot) return

  const root = document.createElement('div')
  root.id = 'gomdown-youtube-overlay'
  root.style.position = 'fixed'
  root.style.right = '20px'
  root.style.bottom = '24px'
  root.style.zIndex = '2147483647'
  root.style.background = 'rgba(17, 21, 32, 0.94)'
  root.style.border = '1px solid rgba(133, 148, 195, 0.35)'
  root.style.borderRadius = '12px'
  root.style.padding = '10px'
  root.style.boxShadow = '0 8px 24px rgba(0, 0, 0, 0.28)'
  root.style.backdropFilter = 'blur(6px)'
  root.style.width = '220px'
  root.style.fontFamily = 'ui-sans-serif, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif'
  root.style.color = '#e8edff'

  const title = document.createElement('div')
  title.textContent = 'Gdown Helper'
  title.style.fontSize = '12px'
  title.style.fontWeight = '700'
  title.style.marginBottom = '8px'

  const action = document.createElement('button')
  action.type = 'button'
  action.textContent = '이 영상 다운로드'
  action.style.width = '100%'
  action.style.height = '34px'
  action.style.border = '1px solid #5a69f0'
  action.style.borderRadius = '8px'
  action.style.background = '#5a69f0'
  action.style.color = '#ffffff'
  action.style.fontSize = '12px'
  action.style.fontWeight = '700'
  action.style.cursor = 'pointer'
  action.addEventListener('click', () => {
    void enqueueCurrentYoutubePage()
  })

  const status = document.createElement('div')
  status.textContent = '클릭 시 gdown 다운로드 모달로 연결'
  status.style.fontSize = '11px'
  status.style.marginTop = '8px'
  status.style.lineHeight = '1.35'
  status.style.color = '#aeb7d8'

  root.appendChild(title)
  root.appendChild(action)
  root.appendChild(status)
  document.documentElement.appendChild(root)

  ytOverlayRoot = root
  ytOverlayStatus = status
}

function watchYoutubeRouteChanges(): void {
  if (ytUrlWatcherTimer !== null) return
  ytUrlWatcherTimer = window.setInterval(() => {
    const current = window.location.href
    if (current === lastObservedUrl) return
    lastObservedUrl = current
    ensureYoutubeOverlay()
  }, 800)

  window.addEventListener('popstate', () => {
    lastObservedUrl = window.location.href
    ensureYoutubeOverlay()
  })

  document.addEventListener('yt-navigate-finish', () => {
    lastObservedUrl = window.location.href
    ensureYoutubeOverlay()
  })
}

ensureYoutubeOverlay()
watchYoutubeRouteChanges()

function findSelectedAnchorUrl(): string {
  const selection = window.getSelection()
  const range = selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : null
  const node = range?.commonAncestorContainer || document.activeElement || null
  const element = node instanceof Element ? node : node?.parentElement || null
  if (!element) return ''
  const anchor = element.closest('a[href]') as HTMLAnchorElement | null
  return String(anchor?.href || '').trim()
}

function setQuickPanelStatus(message: string, tone: 'idle' | 'ok' | 'error' = 'idle'): void {
  if (!quickPanelStatus) return
  quickPanelStatus.textContent = message
  if (tone === 'ok') quickPanelStatus.style.color = '#92f0ad'
  else if (tone === 'error') quickPanelStatus.style.color = '#ffaaaa'
  else quickPanelStatus.style.color = '#aeb7d8'
}

async function onQuickSendCurrentPage(): Promise<void> {
  setQuickPanelStatus('현재 페이지 전송 중...')
  try {
    const result = (await browser.runtime.sendMessage({
      type: 'page:enqueue-ytdlp-url',
      url: window.location.href,
      referer: window.location.href,
    })) as { ok?: boolean; error?: string }
    if (!result?.ok) {
      setQuickPanelStatus(`실패: ${result?.error || 'unknown error'}`, 'error')
      return
    }
    setQuickPanelStatus('현재 페이지 전송 완료', 'ok')
  } catch (error) {
    setQuickPanelStatus(`실패: ${String(error)}`, 'error')
  }
}

async function onQuickSendSelectedTarget(): Promise<void> {
  const selectedLink = findSelectedAnchorUrl()
  if (selectedLink) {
    setQuickPanelStatus('선택 링크 전송 중...')
    try {
      const linkResult = (await browser.runtime.sendMessage({
        type: 'page:enqueue-ytdlp-url',
        url: selectedLink,
        referer: window.location.href,
      })) as { ok?: boolean; error?: string }
      if (linkResult?.ok) {
        setQuickPanelStatus('선택 링크 전송 완료', 'ok')
        return
      }
      setQuickPanelStatus(`실패: ${linkResult?.error || 'unknown error'}`, 'error')
      return
    } catch (error) {
      setQuickPanelStatus(`실패: ${String(error)}`, 'error')
    }
  }

  const clipResult = await createClipFromCurrentSelection()
  if (clipResult.ok) {
    setQuickPanelStatus('선택 텍스트 클립 저장', 'ok')
    return
  }
  setQuickPanelStatus('선택 링크/텍스트가 없습니다.', 'error')
}

function removeQuickPanel(): void {
  if (!quickPanelRoot) return
  quickPanelRoot.remove()
  quickPanelRoot = null
  quickPanelStatus = null
}

function hideQuickActionBar(): void {
  if (!quickActionBarRoot) return
  quickActionBarRoot.style.display = 'none'
  if (quickActionBarTimer !== null) {
    window.clearTimeout(quickActionBarTimer)
    quickActionBarTimer = null
  }
}

function showQuickActionBar(): void {
  if (!extensionEnabled) return
  if (!quickActionBarRoot) {
    const root = document.createElement('div')
    root.id = 'gomdown-quick-action-bar'
    root.style.position = 'fixed'
    root.style.left = '50%'
    root.style.bottom = '16px'
    root.style.transform = 'translateX(-50%)'
    root.style.zIndex = '2147483647'
    root.style.display = 'flex'
    root.style.gap = '8px'
    root.style.padding = '10px'
    root.style.background = 'rgba(15, 19, 29, 0.94)'
    root.style.border = '1px solid rgba(101, 116, 158, 0.48)'
    root.style.borderRadius = '12px'
    root.style.boxShadow = '0 12px 24px rgba(0, 0, 0, 0.28)'
    root.style.backdropFilter = 'blur(6px)'

    const makeBtn = (label: string, onClick: () => void, primary = false): HTMLButtonElement => {
      const btn = document.createElement('button')
      btn.type = 'button'
      btn.textContent = label
      btn.style.height = '32px'
      btn.style.padding = '0 12px'
      btn.style.borderRadius = '8px'
      btn.style.border = primary ? '1px solid #5968f2' : '1px solid #4a556f'
      btn.style.background = primary ? '#5968f2' : '#273041'
      btn.style.color = '#e8edff'
      btn.style.fontSize = '12px'
      btn.style.fontWeight = '700'
      btn.style.cursor = 'pointer'
      btn.addEventListener('click', onClick)
      return btn
    }

    const extraWrap = document.createElement('div')
    extraWrap.style.display = 'none'
    extraWrap.style.gap = '6px'
    extraWrap.style.alignItems = 'center'
    extraWrap.style.flexWrap = 'wrap'

    const exportMdBtn = makeBtn('MD', () => {
      void browser.runtime
        .sendMessage({
          type: 'clip:export-current-page-md',
          pageUrl: window.location.href,
          pageTitle: document.title || window.location.href,
        })
        .then((result: any) => {
          setQuickPanelStatus(result?.ok ? 'MD 내보내기 완료' : `실패: ${result?.error || 'unknown error'}`, result?.ok ? 'ok' : 'error')
        })
        .catch((error) => {
          setQuickPanelStatus(`실패: ${String(error)}`, 'error')
        })
    })
    const exportJsonBtn = makeBtn('JSON', () => {
      void browser.runtime
        .sendMessage({
          type: 'clip:export-current-page-json',
          pageUrl: window.location.href,
          pageTitle: document.title || window.location.href,
        })
        .then((result: any) => {
          setQuickPanelStatus(result?.ok ? 'JSON 내보내기 완료' : `실패: ${result?.error || 'unknown error'}`, result?.ok ? 'ok' : 'error')
        })
        .catch((error) => {
          setQuickPanelStatus(`실패: ${String(error)}`, 'error')
        })
    })
    const obsidianBtn = makeBtn('Obsidian', () => {
      void browser.runtime
        .sendMessage({
          type: 'clip:send-obsidian-current-page',
          pageUrl: window.location.href,
          pageTitle: document.title || window.location.href,
        })
        .then((result: any) => {
          if (!result?.ok || !result?.uri) {
            setQuickPanelStatus(`실패: ${result?.error || 'unknown error'}`, 'error')
            return
          }
          try {
            window.open(String(result.uri), '_blank')
          } catch {
            window.location.href = String(result.uri)
          }
          setQuickPanelStatus('Obsidian 전송 시도 완료', 'ok')
        })
        .catch((error) => {
          setQuickPanelStatus(`실패: ${String(error)}`, 'error')
        })
    })
    extraWrap.appendChild(exportMdBtn)
    extraWrap.appendChild(exportJsonBtn)
    extraWrap.appendChild(obsidianBtn)

    root.appendChild(makeBtn('현재 페이지', () => { void onQuickSendCurrentPage() }))
    root.appendChild(makeBtn('선택 항목', () => { void onQuickSendSelectedTarget() }, true))
    root.appendChild(
      makeBtn('클립 저장', () => {
        void createClipFromCurrentSelection().then((result) => {
          if (result.ok) extraWrap.style.display = 'flex'
        })
      })
    )
    root.appendChild(makeBtn('닫기', () => { hideQuickActionBar() }))
    root.appendChild(extraWrap)
    document.documentElement.appendChild(root)
    quickActionBarRoot = root
  }

  quickActionBarRoot.style.display = 'flex'
  if (quickActionBarTimer !== null) window.clearTimeout(quickActionBarTimer)
  quickActionBarTimer = window.setTimeout(() => {
    hideQuickActionBar()
  }, 8000)
}

function ensureQuickPanel(): void {
  if (!extensionEnabled) {
    removeQuickPanel()
    hideQuickActionBar()
    return
  }
  if (window.top !== window.self) return
  if (quickPanelRoot) return

  const root = document.createElement('div')
  root.id = 'gomdown-quick-panel'
  root.style.position = 'fixed'
  root.style.right = '14px'
  root.style.top = '42%'
  root.style.transform = 'translateY(-42%)'
  root.style.width = '188px'
  root.style.zIndex = '2147483647'
  root.style.padding = '10px'
  root.style.borderRadius = '12px'
  root.style.background = 'rgba(16, 20, 30, 0.94)'
  root.style.border = '1px solid rgba(110, 125, 168, 0.42)'
  root.style.boxShadow = '0 12px 24px rgba(0, 0, 0, 0.28)'
  root.style.backdropFilter = 'blur(6px)'
  root.style.display = 'grid'
  root.style.gap = '7px'
  root.style.fontFamily = 'ui-sans-serif, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif'

  const title = document.createElement('div')
  title.textContent = 'Gdown Quick'
  title.style.fontSize = '12px'
  title.style.fontWeight = '700'
  title.style.color = '#e8edff'

  const currentBtn = document.createElement('button')
  currentBtn.type = 'button'
  currentBtn.textContent = '현재 페이지'
  currentBtn.style.height = '30px'
  currentBtn.style.border = '1px solid #5766ef'
  currentBtn.style.borderRadius = '8px'
  currentBtn.style.background = '#5766ef'
  currentBtn.style.color = '#ffffff'
  currentBtn.style.fontSize = '12px'
  currentBtn.style.fontWeight = '700'
  currentBtn.style.cursor = 'pointer'
  currentBtn.addEventListener('click', () => {
    void onQuickSendCurrentPage()
  })

  const selectedBtn = document.createElement('button')
  selectedBtn.type = 'button'
  selectedBtn.textContent = '선택 항목'
  selectedBtn.style.height = '30px'
  selectedBtn.style.border = '1px solid #4d566f'
  selectedBtn.style.borderRadius = '8px'
  selectedBtn.style.background = '#2a3040'
  selectedBtn.style.color = '#dce5ff'
  selectedBtn.style.fontSize = '12px'
  selectedBtn.style.fontWeight = '700'
  selectedBtn.style.cursor = 'pointer'
  selectedBtn.addEventListener('click', () => {
    void onQuickSendSelectedTarget()
  })

  const status = document.createElement('div')
  status.textContent = '현재 페이지/선택 항목 빠른 처리'
  status.style.fontSize = '11px'
  status.style.lineHeight = '1.35'
  status.style.color = '#aeb7d8'

  root.appendChild(title)
  root.appendChild(currentBtn)
  root.appendChild(selectedBtn)
  root.appendChild(status)
  document.documentElement.appendChild(root)

  quickPanelRoot = root
  quickPanelStatus = status
}

function highlightColorByClip(item: ClipItem): string {
  if (item.color === 'yellow') return 'rgba(255, 235, 87, 0.58)'
  return 'rgba(255, 235, 87, 0.58)'
}

function clearClipHighlights(): void {
  const nodes = Array.from(document.querySelectorAll('span[data-gomdown-clip]')) as HTMLSpanElement[]
  for (const span of nodes) {
    const parent = span.parentNode
    if (!parent) continue
    while (span.firstChild) {
      parent.insertBefore(span.firstChild, span)
    }
    parent.removeChild(span)
  }
}

function showClipToast(message: string, tone: 'ok' | 'error' = 'ok'): void {
  const root = ensureMediaToastRoot()
  root.textContent = message
  root.style.display = 'block'
  root.style.borderColor = tone === 'ok' ? 'rgba(123, 190, 124, 0.55)' : 'rgba(200, 113, 113, 0.55)'
  root.style.color = tone === 'ok' ? '#dffbe4' : '#ffe4e4'
  if (mediaToastTimer !== null) window.clearTimeout(mediaToastTimer)
  mediaToastTimer = window.setTimeout(() => {
    root.style.display = 'none'
    mediaToastTimer = null
    root.style.borderColor = 'rgba(128, 140, 180, 0.42)'
    root.style.color = '#dce4fa'
  }, 1800)
}

function applyClipHighlight(item: ClipItem): boolean {
  const range = resolveAnchorToRange(item.anchor)
  if (!range || range.collapsed) return false

  const wrapper = document.createElement('span')
  wrapper.dataset.gomdownClip = item.id
  wrapper.style.background = highlightColorByClip(item)
  wrapper.style.padding = '0.04em 0.03em'
  wrapper.style.borderRadius = '0.12em'
  wrapper.style.boxDecorationBreak = 'clone'
  wrapper.style.setProperty('-webkit-box-decoration-break', 'clone')
  wrapper.style.cursor = 'pointer'
  wrapper.title = item.quote || 'clip'
  wrapper.addEventListener('click', (event) => {
    if (!(event.altKey || event.metaKey || event.ctrlKey)) return
    event.stopPropagation()
    event.preventDefault()
  })

  try {
    const fragment = range.extractContents()
    wrapper.appendChild(fragment)
    range.insertNode(wrapper)
    return true
  } catch {
    return false
  }
}

async function reportClipResolveStatus(id: string, status: 'ok' | 'broken'): Promise<void> {
  await browser.runtime.sendMessage({
    type: 'clip:resolve-status',
    id,
    status,
  }).catch(() => null)
}

function revealClipById(id: string): boolean {
  const target = document.querySelector(`span[data-gomdown-clip="${CSS.escape(id)}"]`) as HTMLElement | null
  if (!target) return false
  target.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' })
  target.animate(
    [
      { boxShadow: '0 0 0 0 rgba(255, 250, 164, 0.2)' },
      { boxShadow: '0 0 0 8px rgba(255, 250, 164, 0.65)' },
      { boxShadow: '0 0 0 0 rgba(255, 250, 164, 0.2)' },
    ],
    { duration: 760, easing: 'ease-out' }
  )
  return true
}

async function revealClipByIdWithRetry(id: string): Promise<boolean> {
  if (revealClipById(id)) return true
  await syncClipsForCurrentPage(true)
  return revealClipById(id)
}

async function syncClipsForCurrentPage(showStatus = false): Promise<void> {
  if (!extensionEnabled) return
  const runId = ++clipSyncRunId
  const pageUrl = normalizePageUrl(window.location.href)
  const delays = [0, 700, 1900]
  let finalApplied = 0
  let finalCount = 0

  for (let attempt = 0; attempt < delays.length; attempt += 1) {
    if (runId !== clipSyncRunId) return
    const delay = delays[attempt]
    if (delay > 0) {
      await new Promise((resolve) => window.setTimeout(resolve, delay))
      if (runId !== clipSyncRunId) return
    }

    const response = (await browser.runtime.sendMessage({
      type: 'clip:list',
      pageUrl,
    })) as { ok?: boolean; items?: ClipItem[] }
    if (!response?.ok || !Array.isArray(response.items)) return

    clearClipHighlights()
    const sorted = [...response.items].sort((a, b) => {
      const aStart = Number(a.anchor.startTextOffset || 0)
      const bStart = Number(b.anchor.startTextOffset || 0)
      return bStart - aStart
    })
    let applied = 0
    const brokenIds: string[] = []
    for (const item of sorted) {
      if (applyClipHighlight(item)) {
        applied += 1
      } else {
        brokenIds.push(item.id)
      }
    }

    finalApplied = applied
    finalCount = sorted.length
    if (brokenIds.length === 0 || attempt === delays.length - 1) {
      const brokenSet = new Set(brokenIds)
      await Promise.all(sorted.map((item) => reportClipResolveStatus(item.id, brokenSet.has(item.id) ? 'broken' : 'ok')))
      break
    }
  }

  if (showStatus) {
    showClipToast(`클립 복원: ${finalApplied}/${finalCount}`, finalApplied > 0 ? 'ok' : 'error')
  }
}

async function createClipFromCurrentSelection(): Promise<{ ok: boolean; error?: string }> {
  if (!extensionEnabled) return { ok: false, error: 'extension disabled' }
  const selection = window.getSelection()
  clipLog('createClipFromCurrentSelection:start', {
    hasSelection: Boolean(selection),
    selectedTextLength: Number(selection?.toString()?.length || 0),
    href: window.location.href,
  })
  if (!selection) return { ok: false, error: 'selection unavailable' }
  const created = createAnchorFromSelection(selection)
  if (!created) {
    showClipToast('선택된 텍스트가 없습니다.', 'error')
    clipLog('createClipFromCurrentSelection:fail', 'empty selection')
    return { ok: false, error: 'empty selection' }
  }

  const response = (await browser.runtime.sendMessage({
    type: 'clip:create',
    pageUrl: normalizePageUrl(window.location.href),
    pageTitle: document.title || window.location.href,
    quote: String(created.quote || '').trim(),
    quoteHtml: selectionToHtml(selection),
    anchor: created.anchor,
  })) as { ok?: boolean; item?: ClipItem; error?: string }

  if (!response?.ok || !response.item) {
    showClipToast(`클립 저장 실패: ${response?.error || 'unknown error'}`, 'error')
    clipLog('createClipFromCurrentSelection:fail', response?.error || 'create failed')
    return { ok: false, error: response?.error || 'create failed' }
  }

  clearClipHighlights()
  await syncClipsForCurrentPage()
  showClipToast('클립 저장 완료 (Alt+Shift+C)', 'ok')
  clipLog('createClipFromCurrentSelection:ok', {
    id: response.item.id,
    pageUrl: response.item.pageUrl,
  })
  return { ok: true }
}

function watchClipRouteChanges(): void {
  if (clipRouteWatcherTimer !== null) return
  clipRouteWatcherTimer = window.setInterval(() => {
    const next = normalizePageUrl(window.location.href)
    if (next === lastClipPageUrl) return
    lastClipPageUrl = next
    void syncClipsForCurrentPage()
  }, 900)
  window.addEventListener('popstate', () => {
    const next = normalizePageUrl(window.location.href)
    if (next === lastClipPageUrl) return
    lastClipPageUrl = next
    void syncClipsForCurrentPage()
  })
}

watchClipRouteChanges()

let mediaToastRoot: HTMLDivElement | null = null
let mediaToastTimer: number | null = null

function hideMediaCapturedToast(): void {
  if (!mediaToastRoot) return
  mediaToastRoot.style.display = 'none'
  if (mediaToastTimer !== null) {
    window.clearTimeout(mediaToastTimer)
    mediaToastTimer = null
  }
}

function ensureMediaToastRoot(): HTMLDivElement {
  if (mediaToastRoot) return mediaToastRoot
  const root = document.createElement('div')
  root.id = 'gomdown-media-toast'
  root.style.position = 'fixed'
  root.style.left = '18px'
  root.style.bottom = '18px'
  root.style.zIndex = '2147483647'
  root.style.maxWidth = '360px'
  root.style.padding = '10px 12px'
  root.style.borderRadius = '10px'
  root.style.border = '1px solid rgba(128, 140, 180, 0.42)'
  root.style.background = 'rgba(18, 21, 31, 0.95)'
  root.style.color = '#dce4fa'
  root.style.fontSize = '12px'
  root.style.lineHeight = '1.35'
  root.style.fontFamily = 'ui-sans-serif, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif'
  root.style.boxShadow = '0 10px 24px rgba(0, 0, 0, 0.28)'
  root.style.display = 'none'
  document.documentElement.appendChild(root)
  mediaToastRoot = root
  return root
}

function showMediaCapturedToast(payload: { kind?: string; url?: string; suggestedOut?: string }): void {
  if (!extensionEnabled) return
  const root = ensureMediaToastRoot()
  const kind = String(payload?.kind || 'media').toUpperCase()
  const out = String(payload?.suggestedOut || '').trim()
  const shortUrl = String(payload?.url || '').trim().slice(0, 96)
  root.textContent = out
    ? `캡처됨 [${kind}] ${out}`
    : `캡처됨 [${kind}] ${shortUrl}${shortUrl.length >= 96 ? '…' : ''}`
  root.style.display = 'block'
  if (mediaToastTimer !== null) window.clearTimeout(mediaToastTimer)
  mediaToastTimer = window.setTimeout(() => {
    root.style.display = 'none'
    mediaToastTimer = null
  }, 2200)
}

browser.runtime.onMessage.addListener((message: any) => {
  if (message?.type === 'clip:ping') {
    clipLog('runtime.onMessage clip:ping')
    return { ok: true }
  }

  if (message?.type === 'clip:create-from-selection') {
    clipLog('runtime.onMessage clip:create-from-selection')
    return createClipFromCurrentSelection()
  }

  if (message?.type === 'clip:show-action-bar') {
    showQuickActionBar()
    return { ok: true }
  }

  if (message?.type === 'media:captured') {
    showMediaCapturedToast({
      kind: message?.kind,
      url: message?.url,
      suggestedOut: message?.suggestedOut,
    })
    return undefined
  }

  if (message?.type === 'clip:reveal') {
    const id = String(message?.id || '').trim()
    if (!id) return undefined
    return revealClipByIdWithRetry(id).then((ok) => ({ ok }))
  }

  return undefined
})

async function syncExtensionEnabled(): Promise<void> {
  try {
    const settings = await getSettings()
    extensionEnabled = Boolean(settings.extensionStatus)
  } catch {
    extensionEnabled = false
  }

  if (!extensionEnabled) {
    removeYoutubeOverlay()
    removeQuickPanel()
    hideQuickActionBar()
    hideMediaCapturedToast()
    clearClipHighlights()
  } else {
    ensureYoutubeOverlay()
    ensureQuickPanel()
    await syncClipsForCurrentPage()
  }
}

void syncExtensionEnabled()

browser.storage.onChanged.addListener((changes, areaName) => {
  if (areaName !== 'sync') return
  if (!changes.extensionStatus) return
  extensionEnabled = Boolean(changes.extensionStatus.newValue)
  if (!extensionEnabled) {
    removeYoutubeOverlay()
    removeQuickPanel()
    hideQuickActionBar()
    hideMediaCapturedToast()
    clearClipHighlights()
    return
  }
  ensureYoutubeOverlay()
  ensureQuickPanel()
  void syncClipsForCurrentPage()
})
