/// <reference lib="dom" />

import { extractHostState, reloadActivityPageForPresentation } from './session-page.js'
import { shouldRefreshLoggedInPresentation } from './session-state.js'

type BrowserPageLike = {
  evaluate: <T = any>(callback: any) => Promise<T>
  waitForTimeout: (milliseconds: number) => Promise<void>
}

type HostState = {
  loginText?: string
  unloginVisible?: boolean
  loginedVisible?: boolean
}

export type ActivityInfo = {
  nickname: string
  role: {
    ready: boolean
    roleName: string
    roleId: string
    area: string
    partition: string
    platId: string
    md5str: string
    checkparam: string
  }
  form: {
    cdkeyInputId: string
    cdkeyValue: string
    verifyInputId: string
    verifyValue: string
    verifyImgId: string
    submitId: string
  }
  verify: {
    visible: boolean
    src: string
    naturalWidth: number
    naturalHeight: number
  }
  popup: {
    visible: boolean
    text: string
    detail: string
  }
}

type SessionForPresentation = {
  page: BrowserPageLike
  presentationSyncAttempts: number
}

type EnsureLoggedInPresentationOptions = {
  hostState?: HostState | null
  credentialReady?: boolean
  activityUrl?: string
  extractHostState?: (page: BrowserPageLike) => Promise<HostState>
  reloadActivityPageForPresentation?: (page: BrowserPageLike, activityUrl: string) => Promise<unknown>
  shouldRefreshLoggedInPresentation?: (hostState: HostState) => boolean
}

type EnsureActivityInfoReadyOptions = {
  triggerRender?: boolean
  timeoutMs?: number
  extractActivityInfo?: (page: BrowserPageLike) => Promise<ActivityInfo>
  triggerActivityRoleRender?: (page: BrowserPageLike) => Promise<unknown>
  now?: () => number
}

declare global {
  interface Window {
    renderCdkey?: () => void
    roleData?: Record<string, unknown>
    isRole?: unknown
  }
}

export function createEmptyActivityInfo(): ActivityInfo {
  return {
    nickname: '',
    role: {
      ready: false,
      roleName: '',
      roleId: '',
      area: '',
      partition: '',
      platId: '',
      md5str: '',
      checkparam: '',
    },
    form: {
      cdkeyInputId: '',
      cdkeyValue: '',
      verifyInputId: '',
      verifyValue: '',
      verifyImgId: '',
      submitId: '',
    },
    verify: {
      visible: false,
      src: '',
      naturalWidth: 0,
      naturalHeight: 0,
    },
    popup: {
      visible: false,
      text: '',
      detail: '',
    },
  }
}

export async function ensureLoggedInPresentation(
  session: SessionForPresentation,
  {
    hostState = null,
    credentialReady = false,
    activityUrl = '',
    extractHostState: extractHostStateImpl = extractHostState,
    reloadActivityPageForPresentation: reloadActivityPageForPresentationImpl = reloadActivityPageForPresentation,
    shouldRefreshLoggedInPresentation: shouldRefreshLoggedInPresentationImpl = shouldRefreshLoggedInPresentation,
  }: EnsureLoggedInPresentationOptions = {},
): Promise<{ reloaded: boolean; hostState: HostState }> {
  let nextHostState = hostState || (await extractHostStateImpl(session.page))

  if (!credentialReady || !shouldRefreshLoggedInPresentationImpl(nextHostState)) {
    return {
      reloaded: false,
      hostState: nextHostState,
    }
  }

  if (session.presentationSyncAttempts >= 2) {
    return {
      reloaded: false,
      hostState: nextHostState,
    }
  }

  session.presentationSyncAttempts += 1
  await reloadActivityPageForPresentationImpl(session.page, activityUrl)
  nextHostState = await extractHostStateImpl(session.page)

  return {
    reloaded: true,
    hostState: nextHostState,
  }
}

export async function ensureActivityInfoReady(
  page: BrowserPageLike,
  {
    triggerRender = false,
    timeoutMs = 8_000,
    extractActivityInfo: extractActivityInfoImpl = extractActivityInfo,
    triggerActivityRoleRender: triggerActivityRoleRenderImpl = triggerActivityRoleRender,
    now = () => Date.now(),
  }: EnsureActivityInfoReadyOptions = {},
): Promise<ActivityInfo> {
  if (triggerRender) {
    await triggerActivityRoleRenderImpl(page)
  }

  const startedAt = now()
  let lastInfo = await extractActivityInfoImpl(page)

  while (now() - startedAt < timeoutMs) {
    if (lastInfo?.role?.ready) {
      return lastInfo
    }

    await page.waitForTimeout(400)
    lastInfo = await extractActivityInfoImpl(page)
  }

  return lastInfo
}

export async function triggerActivityRoleRender(page: BrowserPageLike): Promise<void> {
  try {
    await page.evaluate(() => {
      const maybeRender = window.renderCdkey

      if (typeof maybeRender === 'function') {
        maybeRender()
      }
    })
  } catch {
    // ignore render trigger failures; polling will still inspect the page
  }
}

export async function extractActivityInfo(page: BrowserPageLike): Promise<ActivityInfo> {
  try {
    return await page.evaluate(() => {
      const asString = (value: unknown) => (typeof value === 'string' ? value.trim() : '')
      const isVisible = (element: Element | null) => {
        if (!element) {
          return false
        }

        const style = window.getComputedStyle(element)
        return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0'
      }

      const pick = (selector: string) => document.querySelector(selector)
      const pickBest = (
        selector: string,
        predicate: ((element: Element) => boolean) | null = null,
      ): Element | null => {
        const matches = [...document.querySelectorAll(selector)]
        const preferred = matches.find((element) => {
          if (predicate && !predicate(element)) {
            return false
          }

          return isVisible(element)
        })

        if (preferred) {
          return preferred
        }

        if (predicate) {
          return matches.find((element) => predicate(element)) || null
        }

        return matches[0] || null
      }
      const textOf = (selector) => asString(pick(selector)?.textContent || '')
      const inputOf = (selector) => {
        const element = pickBest(
          selector,
          (node) => node instanceof HTMLInputElement || node instanceof HTMLTextAreaElement,
        )

        if (!(element instanceof HTMLInputElement) && !(element instanceof HTMLTextAreaElement)) {
          return ''
        }

        return asString(element.value || '')
      }

      const roleData = window.roleData && typeof window.roleData === 'object' ? window.roleData : {}
      const roleSelector = pick('[id^="milo_role_selector_"]')
      const roleNameText =
        textOf('#role_name') ||
        asString(roleSelector instanceof HTMLSelectElement ? roleSelector.selectedOptions?.[0]?.textContent || '' : '')
      const verifyImg = pickBest('[id^="milo_verifyImg_"]', (node) => node instanceof HTMLImageElement)
      const popup = pick('#pop2')
      const cdkeyInput = pickBest(
        '[id^="milo_cdkeyInfo_"]',
        (node) =>
          (node instanceof HTMLInputElement || node instanceof HTMLTextAreaElement) &&
          !node.disabled &&
          node.type !== 'hidden',
      )
      const verifyInput = pickBest(
        '[id^="milo_verifyInput_"]',
        (node) =>
          (node instanceof HTMLInputElement || node instanceof HTMLTextAreaElement) &&
          !node.disabled &&
          node.type !== 'hidden',
      )
      const submitButton = pickBest(
        '#milo_cdkey_submit, [id^="milo_cdkey_submit"]',
        (node) => node instanceof HTMLElement && !node.hasAttribute('disabled'),
      )

      return {
        nickname: textOf('#login_nickname_span'),
        role: {
          ready: Boolean(window.isRole || roleNameText),
          roleName: roleNameText,
          roleId: asString(roleData.sRoleId),
          area: asString(roleData.sArea || '36'),
          partition: asString(roleData.sPartition),
          platId: asString(roleData.sPlatId),
          md5str: asString(roleData.sMd5str || roleData.md5str || roleData.sMdStr),
          checkparam: asString(roleData.sCheckparam),
        },
        form: {
          cdkeyInputId: cdkeyInput?.id || '',
          cdkeyValue: inputOf('[id^="milo_cdkeyInfo_"]'),
          verifyInputId: verifyInput?.id || '',
          verifyValue: inputOf('[id^="milo_verifyInput_"]'),
          verifyImgId: verifyImg?.id || '',
          submitId: submitButton?.id || '',
        },
        verify: {
          visible: isVisible(verifyImg),
          src: asString(verifyImg?.getAttribute('src')),
          naturalWidth: verifyImg instanceof HTMLImageElement ? Number(verifyImg.naturalWidth || 0) : 0,
          naturalHeight: verifyImg instanceof HTMLImageElement ? Number(verifyImg.naturalHeight || 0) : 0,
        },
        popup: {
          visible: isVisible(popup),
          text: textOf('#PopText'),
          detail: textOf('#PopText2'),
        },
      }
    })
  } catch {
    return createEmptyActivityInfo()
  }
}
