import type { NavigateFunction } from 'react-router-dom'
import { ISLANDER_CONFIG } from './config'
import {
  capturePageSnapshot,
  findElementByLabel,
  formatPageSnapshot,
  resolveSnapshotElement,
} from './pageSnapshot'
import type {
  IslanderAction,
  IslanderActionHandler,
  IslanderActionResult,
  IslanderExecutorContext,
  PageSnapshot,
} from './types'

const customHandlers = new Map<string, IslanderActionHandler>()

/** Register custom action handlers for future page-specific extensions. */
export function registerIslanderActionHandler(type: string, handler: IslanderActionHandler) {
  customHandlers.set(type, handler)
}

function waitMs(ms: number): Promise<void> {
  return new Promise((resolve) => {
    window.setTimeout(() => resolve(), ms)
  })
}

function setNativeValue(el: HTMLInputElement | HTMLTextAreaElement, value: string) {
  const proto =
    el instanceof HTMLTextAreaElement
      ? window.HTMLTextAreaElement.prototype
      : window.HTMLInputElement.prototype
  const setter = Object.getOwnPropertyDescriptor(proto, 'value')?.set
  const previous = el.value
  setter?.call(el, value)

  const tracker = (el as HTMLInputElement & { _valueTracker?: { setValue: (v: string) => void } })
    ._valueTracker
  if (tracker) {
    tracker.setValue(previous)
  }

  el.dispatchEvent(new Event('input', { bubbles: true }))
  el.dispatchEvent(new Event('change', { bubbles: true }))
}

function resolveFillTarget(
  action: Extract<IslanderAction, { type: 'fill' }>,
  ctx: IslanderExecutorContext,
): HTMLInputElement | HTMLTextAreaElement | null {
  if (action.ref) {
    const byRef = ctx.resolveRef(action.ref)
    if (byRef instanceof HTMLInputElement || byRef instanceof HTMLTextAreaElement) return byRef
  }
  if (action.label) {
    const byLabel = findElementByLabel(action.label)
    if (byLabel instanceof HTMLInputElement || byLabel instanceof HTMLTextAreaElement) return byLabel
  }
  return null
}

function flashHighlight(el: HTMLElement) {
  el.classList.add(ISLANDER_CONFIG.highlightClass)
  window.setTimeout(() => el.classList.remove(ISLANDER_CONFIG.highlightClass), ISLANDER_CONFIG.highlightDurationMs)
}

function isBlockedClick(el: HTMLElement) {
  const label = (el.getAttribute('aria-label') ?? el.textContent ?? '').trim()
  return ISLANDER_CONFIG.blockedClickPatterns.some((pattern) => pattern.test(label))
}

function buildExecutorContext(
  navigate: NavigateFunction,
  snapshot: PageSnapshot,
): IslanderExecutorContext {
  return {
    navigate: (path) => navigate(path),
    snapshot,
    waitMs,
    highlight: flashHighlight,
    resolveRef: (ref) => resolveSnapshotElement(snapshot.refMap, ref),
  }
}

async function runBuiltinAction(
  action: IslanderAction,
  ctx: IslanderExecutorContext,
): Promise<IslanderActionResult> {
  switch (action.type) {
    case 'wait': {
      const ms = Math.min(Math.max(action.ms ?? 300, 0), 3000)
      await ctx.waitMs(ms)
      return { action, ok: true, detail: `waited ${ms}ms` }
    }
    case 'navigate': {
      const path = action.path.startsWith('/') ? action.path : `/${action.path}`
      if (!path.startsWith('/') || path.startsWith('//')) {
        return { action, ok: false, detail: '只允許站內路徑' }
      }
      ctx.navigate(path)
      await ctx.waitMs(ISLANDER_CONFIG.navigateWaitMs)
      return { action, ok: true, detail: path }
    }
    case 'highlight': {
      const el = ctx.resolveRef(action.ref)
      if (!el) return { action, ok: false, detail: '找不到元素' }
      el.scrollIntoView({ block: 'center', behavior: 'smooth' })
      ctx.highlight(el)
      return { action, ok: true, detail: el.textContent?.trim().slice(0, 40) ?? action.ref }
    }
    case 'focus': {
      const el = ctx.resolveRef(action.ref)
      if (!el) return { action, ok: false, detail: '找不到元素' }
      el.scrollIntoView({ block: 'center', behavior: 'smooth' })
      if ('focus' in el && typeof el.focus === 'function') el.focus()
      ctx.highlight(el)
      return { action, ok: true, detail: action.ref }
    }
    case 'scroll': {
      if (action.ref) {
        const el = ctx.resolveRef(action.ref)
        if (!el) return { action, ok: false, detail: '找不到元素' }
        el.scrollIntoView({ block: 'center', behavior: 'smooth' })
        return { action, ok: true, detail: action.ref }
      }
      window.scrollTo({ top: action.top ?? 0, behavior: 'smooth' })
      return { action, ok: true, detail: `top=${action.top ?? 0}` }
    }
    case 'click': {
      const el = ctx.resolveRef(action.ref)
      if (!el) return { action, ok: false, detail: '找不到元素' }
      if (isBlockedClick(el)) return { action, ok: false, detail: '此操作需使用者自行確認' }
      el.scrollIntoView({ block: 'center', behavior: 'smooth' })
      await ctx.waitMs(120)
      ctx.highlight(el)
      el.click()
      await ctx.waitMs(280)
      return { action, ok: true, detail: el.textContent?.trim().slice(0, 40) ?? action.ref }
    }
    case 'fill': {
      const el = resolveFillTarget(action, ctx)
      if (!el) return { action, ok: false, detail: '找不到可填寫的欄位' }
      if (el instanceof HTMLInputElement && el.type === 'password') {
        return { action, ok: false, detail: '密碼欄位請使用者自行輸入' }
      }
      el.scrollIntoView({ block: 'center', behavior: 'smooth' })
      if ('focus' in el && typeof el.focus === 'function') el.focus()
      setNativeValue(el, action.value)
      ctx.highlight(el)
      return { action, ok: true, detail: action.value.slice(0, 40) }
    }
    case 'select': {
      const el = ctx.resolveRef(action.ref)
      if (!el) return { action, ok: false, detail: '找不到元素' }
      if (!(el instanceof HTMLSelectElement)) {
        return { action, ok: false, detail: '此元素不是下拉選單' }
      }
      el.scrollIntoView({ block: 'center', behavior: 'smooth' })
      el.value = action.value
      el.dispatchEvent(new Event('input', { bubbles: true }))
      el.dispatchEvent(new Event('change', { bubbles: true }))
      ctx.highlight(el)
      return { action, ok: true, detail: action.value }
    }
    default:
      return { action, ok: false, detail: '未知操作' }
  }
}

async function runAction(
  action: IslanderAction,
  navigate: NavigateFunction,
  snapshot: PageSnapshot,
): Promise<IslanderActionResult> {
  const ctx = buildExecutorContext(navigate, snapshot)
  const custom = customHandlers.get(action.type)
  if (custom) {
    const result = await custom(action, ctx)
    if (result) return result
  }
  return runBuiltinAction(action, ctx)
}

type ExecuteOptions = {
  actions: IslanderAction[]
  navigate: NavigateFunction
  snapshot?: PageSnapshot
}

export async function executeIslanderActions(opts: ExecuteOptions): Promise<{
  results: IslanderActionResult[]
  snapshotText: string
}> {
  const snapshot = opts.snapshot ?? capturePageSnapshot()
  const results: IslanderActionResult[] = []

  for (const action of opts.actions) {
    const result = await runAction(action, opts.navigate, snapshot)
    results.push(result)
    if (!result.ok && action.type !== 'wait') break
  }

  await waitMs(200)
  return {
    results,
    snapshotText: formatPageSnapshot(capturePageSnapshot()),
  }
}