/* eslint-disable react-refresh/only-export-components */
import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useRef,
  useState,
  type ReactNode,
} from 'react'
import type { CombatLogEntry, PartyMember, Spell } from './game'
import {
  getNativeDisplays,
  hasNativeDualScreenBridge,
  openNativeTopDisplay,
} from './nativeDualScreen'
import {
  dispatchExternalGameAction,
  type ControllerIconStyle,
  type InputAction,
} from './input'
import { ControllerBindingLabel } from './components/ControllerIcons'

const STORAGE_KEY = 'ashen-halls-dual-screen-enabled'
const SNAPSHOT_KEY = 'ashen-halls-dual-screen-snapshot'
const STARTUP_CHOICE_KEY = 'ashen-halls-dual-screen-startup-choice'
const CHANNEL_NAME = 'ashen-halls-dual-screen'

export type DualScreenCombatState = {
  difficultyName: string
  dungeonName: string
  contentName: string
  encounterName: string
  encounterDescription: string
  encounterHealth: number
  encounterMaxHealth: number
  encounterIsBoss: boolean
  encounterIndex: number
  encounterCount: number
  party: PartyMember[]
  partySize: number
  selectedId: string
  log: CombatLogEntry[]
  status: 'playing' | 'won' | 'lost' | 'part-complete' | 'upgrade-choice'
  resource: number
  maxResource: number
  resourceName: string
  playerIsAlive: boolean
  spells: Array<(Spell & { slotIndex: number; remaining: number }) | null>
  activeDevice: 'pc' | 'controller'
  bindings: Record<InputAction, string>
  controllerIconStyle: ControllerIconStyle
  directPartyTargeting: boolean
  paused: boolean
  targetGroup: 0 | 1 | 2
  speedMultiplier: 1 | 2
}

export type DualScreenWorkshopState = {
  mode: 'class' | 'equipment' | 'crafting' | 'talents'
  title: string
  subtitle: string
  summary?: string
  items: Array<{
    glyph?: string
    title: string
    meta?: string
    detail?: string
    status?: string
  }>
}

type DualScreenMessage =
  | { type: 'combat-state'; state: DualScreenCombatState }
  | { type: 'workshop-state'; state: DualScreenWorkshopState }
  | { type: 'companion-ready' }
  | { type: 'companion-heartbeat' }
  | { type: 'control-action'; action: InputAction }
  | { type: 'combat-ended' }
  | { type: 'workshop-ended' }

type DualScreenContextValue = {
  enabled: boolean
  connected: boolean
  setEnabled: (enabled: boolean) => void
  openTopDisplay: () => Promise<boolean>
}

const DualScreenContext = createContext<DualScreenContextValue | null>(null)

function createChannel() {
  return typeof BroadcastChannel === 'undefined'
    ? null
    : new BroadcastChannel(CHANNEL_NAME)
}

function saveSnapshot(state: DualScreenCombatState) {
  try {
    localStorage.setItem(SNAPSHOT_KEY, JSON.stringify({
      savedAt: Date.now(),
      state,
    }))
  } catch {
    // Live BroadcastChannel updates still work if storage is unavailable.
  }
}

function loadRecentSnapshot() {
  try {
    const snapshot = JSON.parse(localStorage.getItem(SNAPSHOT_KEY) ?? 'null') as {
      savedAt: number
      state: DualScreenCombatState
    } | null
    if (!snapshot || Date.now() - snapshot.savedAt > 15000) return null
    return snapshot.state
  } catch {
    return null
  }
}

function memberHotEffects(member: PartyMember) {
  if (member.hotEffects?.length) return member.hotEffects
  return member.hotTicks > 0
    ? [{ id: 'legacy-renew', spellId: 'legacy-renew', label: 'Renew', ticks: member.hotTicks, power: 6 }]
    : []
}

export function DualScreenProvider({ children }: { children: ReactNode }) {
  const [enabled, setEnabledState] = useState(
    () => localStorage.getItem(STORAGE_KEY) === 'true',
  )
  const [connected, setConnected] = useState(false)
  const heartbeatRef = useRef(0)

  const setEnabled = useCallback((nextEnabled: boolean) => {
    localStorage.setItem(STORAGE_KEY, String(nextEnabled))
    setEnabledState(nextEnabled)
    if (!nextEnabled) setConnected(false)
  }, [])

  const openTopDisplay = useCallback(async () => {
    setEnabled(true)
    if (hasNativeDualScreenBridge()) {
      try {
        await openNativeTopDisplay()
        return true
      } catch {
        return false
      }
    }
    const url = new URL(window.location.href)
    url.searchParams.set('display', 'bottom')
    const companion = window.open(
      url.toString(),
      'ashen-halls-top-display',
      'popup=yes,width=1280,height=720',
    )
    companion?.focus()
    return Boolean(companion)
  }, [setEnabled])

  useEffect(() => {
    const channel = createChannel()
    if (!channel) return
    channel.onmessage = (event: MessageEvent<DualScreenMessage>) => {
      if (
        event.data.type !== 'companion-ready'
        && event.data.type !== 'companion-heartbeat'
      ) return
      heartbeatRef.current = Date.now()
      setConnected(true)
    }
    const timer = window.setInterval(() => {
      if (Date.now() - heartbeatRef.current > 3500) setConnected(false)
    }, 1000)
    return () => {
      window.clearInterval(timer)
      channel.close()
    }
  }, [])

  const value = useMemo(
    () => ({ enabled, connected, setEnabled, openTopDisplay }),
    [connected, enabled, openTopDisplay, setEnabled],
  )

  return (
    <DualScreenContext.Provider value={value}>
      {children}
    </DualScreenContext.Provider>
  )
}

export function useDualScreen() {
  const context = useContext(DualScreenContext)
  if (!context) throw new Error('useDualScreen must be used inside DualScreenProvider')
  return context
}

export function DualScreenStartupPrompt() {
  const { openTopDisplay, setEnabled } = useDualScreen()
  const [visible, setVisible] = useState(false)
  const [displayCount, setDisplayCount] = useState<number | null>(null)
  const [message, setMessage] = useState('')
  const autoOpenedRef = useRef(false)

  useEffect(() => {
    if (!hasNativeDualScreenBridge()) return
    if (new URLSearchParams(window.location.search).has('display')) return
    const choice = localStorage.getItem(STARTUP_CHOICE_KEY)
    if (choice === 'yes') {
      if (autoOpenedRef.current) return
      autoOpenedRef.current = true
      openTopDisplay().catch(() => {
        // Settings can still launch the display manually if Android rejects startup launch.
      })
      return
    }
    if (choice === 'no') return
    getNativeDisplays()
      .then((result) => setDisplayCount(result.displays.length))
      .catch(() => setDisplayCount(null))
      .finally(() => setVisible(true))
  }, [openTopDisplay])

  async function enableDualScreen() {
    localStorage.setItem(STARTUP_CHOICE_KEY, 'yes')
    setMessage('Opening second display...')
    const opened = await openTopDisplay()
    if (opened) {
      setVisible(false)
      return
    }
    setMessage('No second display found. Check Thor display mode, then try again.')
  }

  function skipDualScreen() {
    localStorage.setItem(STARTUP_CHOICE_KEY, 'no')
    setEnabled(false)
    setVisible(false)
  }

  if (!visible) return null

  return (
    <div className="dual-startup-prompt" role="dialog" aria-modal="true">
      <section>
        <p className="eyebrow">Display Setup</p>
        <h2>Use Dual-Screen Mode?</h2>
        <p>
          Choose yes on AYN Thor. The game opens the combat view on the upper
          display and keeps controls on the lower display.
        </p>
        {displayCount !== null && (
          <small>{displayCount} Android display{displayCount === 1 ? '' : 's'} detected.</small>
        )}
        {message && <small>{message}</small>}
        <div>
          <button onClick={enableDualScreen} type="button">Yes, Enable</button>
          <button onClick={skipDualScreen} type="button">No</button>
        </div>
      </section>
    </div>
  )
}

export function useDualScreenPublisher(
  state: DualScreenCombatState,
  enabled: boolean,
) {
  const stateRef = useRef(state)
  useEffect(() => {
    stateRef.current = state
  }, [state])

  useEffect(() => {
    if (!enabled) return
    const channel = createChannel()
    if (!channel) return
    const publish = () => channel.postMessage({
      type: 'combat-state',
      state: stateRef.current,
    } satisfies DualScreenMessage)
    channel.onmessage = (event: MessageEvent<DualScreenMessage>) => {
      if (event.data.type === 'companion-ready') publish()
      if (event.data.type === 'control-action') {
        dispatchExternalGameAction(event.data.action, 'controller')
      }
    }
    publish()
    return () => {
      channel.postMessage({ type: 'combat-ended' } satisfies DualScreenMessage)
      channel.close()
    }
  }, [enabled])

  useEffect(() => {
    if (!enabled) return
    saveSnapshot(state)
    const channel = createChannel()
    channel?.postMessage({ type: 'combat-state', state } satisfies DualScreenMessage)
    channel?.close()
  }, [enabled, state])
}

export function useDualScreenWorkshopPublisher(
  state: DualScreenWorkshopState | null,
  enabled: boolean,
) {
  const stateRef = useRef(state)
  useEffect(() => {
    stateRef.current = state
  }, [state])

  useEffect(() => {
    if (!enabled || !state) return
    const channel = createChannel()
    if (!channel) return
    const publish = () => {
      if (stateRef.current) {
        channel.postMessage({
          type: 'workshop-state',
          state: stateRef.current,
        } satisfies DualScreenMessage)
      }
    }
    channel.onmessage = (event: MessageEvent<DualScreenMessage>) => {
      if (event.data.type === 'companion-ready') publish()
    }
    publish()
    return () => {
      channel.postMessage({ type: 'workshop-ended' } satisfies DualScreenMessage)
      channel.close()
    }
  }, [enabled, state])

  useEffect(() => {
    if (!enabled || !state) return
    const channel = createChannel()
    channel?.postMessage({ type: 'workshop-state', state } satisfies DualScreenMessage)
    channel?.close()
  }, [enabled, state])
}

export function DualScreenBottomDisplay() {
  const [state, setState] = useState<DualScreenCombatState | null>(loadRecentSnapshot)
  const [workshopState, setWorkshopState] = useState<DualScreenWorkshopState | null>(null)

  useEffect(() => {
    const channel = createChannel()
    if (!channel) return
    const announce = () => channel.postMessage({ type: 'companion-ready' } satisfies DualScreenMessage)
    channel.onmessage = (event: MessageEvent<DualScreenMessage>) => {
      if (event.data.type === 'combat-state') {
        setState(event.data.state)
        setWorkshopState(null)
      }
      if (event.data.type === 'workshop-state') {
        setWorkshopState(event.data.state)
        setState(null)
      }
      if (event.data.type === 'combat-ended') setState(null)
      if (event.data.type === 'workshop-ended') setWorkshopState(null)
    }
    announce()
    const timer = window.setInterval(() => {
      channel.postMessage({ type: 'companion-heartbeat' } satisfies DualScreenMessage)
    }, 1500)
    return () => {
      window.clearInterval(timer)
      channel.close()
    }
  }, [])

  function sendAction(action: InputAction) {
    const channel = createChannel()
    channel?.postMessage({ type: 'control-action', action } satisfies DualScreenMessage)
    channel?.close()
  }

  if (!state && workshopState) {
    return (
      <main className="dual-bottom-display workshop-bottom-display">
        <header className="dual-controls-header">
          <div>
            <p className="eyebrow">{workshopState.mode}</p>
            <h1>{workshopState.title}</h1>
          </div>
          <div className="dual-controls-progress">
            <span>{workshopState.subtitle}</span>
          </div>
        </header>
        {workshopState.summary && (
          <section className="workshop-bottom-summary">
            {workshopState.summary}
          </section>
        )}
        <section className="workshop-bottom-grid">
          {workshopState.items.map((item, index) => (
            <article key={`${item.title}-${index}`}>
              {item.glyph && <span>{item.glyph}</span>}
              <div>
                <strong>{item.title}</strong>
                {item.meta && <small>{item.meta}</small>}
                {item.detail && <p>{item.detail}</p>}
              </div>
              {item.status && <i>{item.status}</i>}
            </article>
          ))}
        </section>
      </main>
    )
  }

  if (!state) {
    return (
      <main className="dual-bottom-display dual-bottom-waiting">
        <section>
          <p className="eyebrow">Dual-Screen HUD</p>
          <h1>Waiting for Combat</h1>
          <p>Choose a dungeon or raid on the upper screen.</p>
        </section>
      </main>
    )
  }

  return (
    <main className="dual-bottom-display">
      <header className="dual-controls-header">
        <div>
          <p className="eyebrow">{state.difficultyName} {state.contentName}</p>
          <h1>{state.dungeonName}</h1>
        </div>
        <div className="dual-controls-progress">
          <span>Encounter {state.encounterIndex + 1}/{state.encounterCount}</span>
        </div>
      </header>

      <section className="dual-controls-resource">
        <div>
          <p className="eyebrow">Active Target</p>
          <strong>
            {state.party.find((member) => member.id === state.selectedId)?.name ?? 'No Target'}
          </strong>
        </div>
        <div className="dual-controls-mana">
          <span>{state.resourceName} {Math.floor(state.resource)} / {state.maxResource}</span>
          {state.speedMultiplier === 2 && <strong className="speed-badge">2x speed</strong>}
          <div className="bar mana-bar">
            <span style={{ width: `${(state.resource / state.maxResource) * 100}%` }} />
          </div>
        </div>
      </section>

      <div className={`dual-controls-targets ${state.directPartyTargeting ? 'direct' : ''}`}>
        {state.directPartyTargeting ? (
          <>
            {([1, 2, 3, 4, 5, 6] as const).map((slot) => {
              const action = `targetParty${slot}` as InputAction
              const memberIndex = slot - 1 + (state.partySize > 6 ? state.targetGroup * 6 : 0)
              return (
                <button onClick={() => sendAction(action)} type="button" key={action}>
                  <ControllerBindingLabel
                    binding={state.bindings[action]}
                    iconStyle={state.controllerIconStyle}
                  />{' '}
                  {state.party[memberIndex]?.name ?? `Party ${slot}`}
                </button>
              )
            })}
            {state.partySize > 6 && (
              <button onClick={() => sendAction('toggleTargetGroup')} type="button">
                <ControllerBindingLabel
                  binding={state.bindings.toggleTargetGroup}
                  iconStyle={state.controllerIconStyle}
                />{' '}
                Party Group {state.targetGroup + 1}/{Math.ceil(state.partySize / 6)}
              </button>
            )}
          </>
        ) : (
          <>
            <button onClick={() => sendAction('previousTarget')} type="button">
              <ControllerBindingLabel
                binding={state.bindings.previousTarget}
                iconStyle={state.controllerIconStyle}
              /> Previous Target
            </button>
            <button onClick={() => sendAction('nextTarget')} type="button">
              Next Target <ControllerBindingLabel
                binding={state.bindings.nextTarget}
                iconStyle={state.controllerIconStyle}
              />
            </button>
          </>
        )}
      </div>

      <section className="dual-controls-spells">
        {state.spells.map((spell, slotIndex) => {
          if (!spell) {
            return (
              <div className="spell empty-spell" key={`empty-${slotIndex}`}>
                <kbd>{slotIndex + 1}</kbd>
                <strong>Empty</strong>
              </div>
            )
          }
          const action = `ability${slotIndex + 1}` as InputAction
          return (
            <button
              className="spell"
              disabled={
                !state.playerIsAlive
                || state.resource < spell.cost
                || spell.remaining > 0
                || state.status !== 'playing'
                || state.paused
              }
              key={spell.id}
              onClick={() => sendAction(action)}
              type="button"
            >
              <kbd>
                <ControllerBindingLabel
                  binding={state.bindings[action]}
                  compact
                  iconStyle={state.controllerIconStyle}
                />
              </kbd>
              <span className={`spell-icon spell-${spell.kind}`}>{spell.glyph}</span>
              <strong>{spell.name}</strong>
              <small>{spell.cost} {state.resourceName}</small>
              {spell.remaining > 0 && <i>{spell.remaining.toFixed(1)}</i>}
            </button>
          )
        })}
      </section>
    </main>
  )
}

export function DualScreenTopCombat({
  state,
  onSelectTarget,
}: {
  state: DualScreenCombatState
  onSelectTarget: (id: string) => void
}) {
  const enemyPercent = Math.max(
    0,
    (state.encounterHealth / state.encounterMaxHealth) * 100,
  )

  return (
    <div className="dual-top-main">
      <section className="dual-top-enemy">
        <div className="enemy-portrait" aria-hidden="true">
          {state.encounterIsBoss ? 'B' : 'M'}
        </div>
        <div className="enemy-info">
          <div className="bar-label">
            <strong>{state.encounterName}</strong>
            <span>{Math.ceil(state.encounterHealth)} / {state.encounterMaxHealth}</span>
          </div>
          <div className="bar enemy-health">
            <span style={{ width: `${enemyPercent}%` }} />
          </div>
          <p>{state.encounterDescription}</p>
        </div>
      </section>

      <section className="dual-top-party">
        <div className={`dual-top-party-grid ${state.partySize > 6 ? 'raid' : ''}`}>
          {state.party.map((member, index) => {
            const partySlot = (index % 6) + 1
            const targetAction = `targetParty${partySlot}` as InputAction
            const groupStart = state.partySize > 6 ? state.targetGroup * 6 : 0
            const inCurrentTargetGroup = index >= groupStart && index < groupStart + 6
            const targetBinding = state.directPartyTargeting && inCurrentTargetGroup ? state.bindings[targetAction] : null
            return (
              <button
                className={`dual-top-member ${state.selectedId === member.id ? 'selected' : ''} ${member.health <= 0 ? 'dead' : ''}`}
                data-party-member-id={member.id}
                key={member.id}
                onClick={() => onSelectTarget(member.id)}
                type="button"
              >
                <div className="member-header">
                  <span className={`role role-${member.role.toLowerCase()}`}>{member.role[0]}</span>
                  <strong>{member.name}</strong>
                  <small>{Math.ceil(member.health)} / {member.maxHealth}</small>
                </div>
                <div className="bar member-health">
                  <span style={{ width: `${(member.health / member.maxHealth) * 100}%` }} />
                  {member.shield > 0 && (
                    <i style={{ width: `${(member.shield / member.maxHealth) * 100}%` }} />
                  )}
                  <em className="health-text">{Math.ceil(member.health)} / {member.maxHealth}</em>
                </div>
                {state.directPartyTargeting && targetBinding && (
                  <div className="member-target-key">
                    <ControllerBindingLabel
                      binding={targetBinding}
                      iconStyle={state.controllerIconStyle}
                    />
                  </div>
                )}
                <div className="member-effects">
                  {memberHotEffects(member).map((effect) => (
                    <span className="buff" key={effect.id}>{effect.label}</span>
                  ))}
                  {member.debuff && <span className="debuff">{member.debuff}</span>}
                </div>
              </button>
            )
          })}
        </div>
      </section>

    </div>
  )
}
