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

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'
const COMBAT_PUBLISH_INTERVAL_MS = 100
const COMBAT_SNAPSHOT_INTERVAL_MS = 1000

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[]
  opponentName?: string
  opponentClassName?: string
  opponentParty?: PartyMember[]
  opponentEnemyHealth?: number
  opponentArena?: {
    bounds: { width: number, height: number }
    bosses: Array<{ id: string, name: string, icon: string, color: string, x: number, y: number, radius: number, health: number, maxHealth: number }>
    party: Array<{ id: string, icon: string, color: string, x: number, y: number, radius: number, health: number, maxHealth: number, isHealer: boolean }>
  }
  opponentResource?: number
  opponentMaxResource?: number
  opponentResourceName?: string
  opponentBuffSummary?: string
  opponentDebuffSummary?: string
  floatingTexts: Array<{
    id: number
    memberId: string
    value: number
  }>
  partySize: number
  selectedId: string
  status: 'playing' | 'won' | 'lost' | 'part-complete' | 'marathon-choice' | 'upgrade-choice'
  resource: number
  maxResource: number
  resourceName: string
  playerIsAlive: boolean
  spells: Array<(Spell & { slotIndex: number; remaining: number }) | null>
  bindings: Record<InputAction, string>
  controllerIconStyle: ControllerIconStyle
  directPartyTargeting: boolean
  paused: boolean
  targetGroup: 0 | 1 | 2
  speedMultiplier: 1 | 2
  stadium?: {
    dampeningPercent: number
    roundIndex: number
    playerWins: number
    opponentWins: number
    survivalSeconds: number
    opponentSurvivalSeconds: number
  }
}

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

export type DualScreenSetupState = {
  contentType: 'dungeon' | 'raid'
  title: string
  subtitle: string
  description: string
  initials: string
  difficultyName: string
  itemLevel: number
  experience: number
  lockedReason?: string
  stats: {
    health: string
    damage: string
    xp: string
    loot: string
  }
}

type DualScreenMessage =
  | { type: 'combat-state'; state: DualScreenCombatState }
  | { type: 'workshop-state'; state: DualScreenWorkshopState }
  | { type: 'setup-state'; state: DualScreenSetupState }
  | { type: 'companion-ready' }
  | { type: 'companion-heartbeat' }
  | { type: 'control-action'; action: InputAction }
  | { type: 'combat-ended' }
  | { type: 'workshop-ended' }
  | { type: 'setup-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 formatDualTime(seconds: number) {
  const total = Math.max(0, Math.floor(seconds))
  return `${Math.floor(total / 60)}:${String(total % 60).padStart(2, '0')}`
}

function shouldRelayBottomAction(
  action: InputAction,
  state: DualScreenCombatState | null,
) {
  if (!state) return false
  if (action === 'confirm' || action === 'toggleTouchLock') return false
  if (state.status === 'playing') return true
  return action === 'pause' || action === 'back'
}

function shouldRelaySetupAction(
  action: InputAction,
  state: DualScreenSetupState | null,
) {
  if (!state) return false
  return action.startsWith('navigate') || action === 'confirm' || action === 'back'
}

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()
    saveSnapshot(stateRef.current)
    const publishTimer = window.setInterval(publish, COMBAT_PUBLISH_INTERVAL_MS)
    const snapshotTimer = window.setInterval(() => {
      saveSnapshot(stateRef.current)
    }, COMBAT_SNAPSHOT_INTERVAL_MS)
    return () => {
      window.clearInterval(publishTimer)
      window.clearInterval(snapshotTimer)
      saveSnapshot(stateRef.current)
      channel.postMessage({ type: 'combat-ended' } satisfies DualScreenMessage)
      channel.close()
    }
  }, [enabled])
}

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()
      if (event.data.type === 'control-action') {
        dispatchExternalGameAction(event.data.action, 'controller')
      }
    }
    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 useDualScreenSetupPublisher(
  state: DualScreenSetupState | 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: 'setup-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: 'setup-ended' } satisfies DualScreenMessage)
      channel.close()
    }
  }, [enabled, state])

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

export function DualScreenBottomDisplay() {
  const [state, setState] = useState<DualScreenCombatState | null>(loadRecentSnapshot)
  const [workshopState, setWorkshopState] = useState<DualScreenWorkshopState | null>(null)
  const [setupState, setSetupState] = useState<DualScreenSetupState | 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)
        setSetupState(null)
      }
      if (event.data.type === 'workshop-state') {
        setWorkshopState(event.data.state)
        setState(null)
        setSetupState(null)
      }
      if (event.data.type === 'setup-state') {
        setSetupState(event.data.state)
        setState(null)
        setWorkshopState(null)
      }
      if (event.data.type === 'combat-ended') setState(null)
      if (event.data.type === 'workshop-ended') setWorkshopState(null)
      if (event.data.type === 'setup-ended') setSetupState(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()
  }

  useGameAction((action) => {
    if (
      !shouldRelayBottomAction(action, state)
      && !shouldRelaySetupAction(action, setupState)
    ) return
    sendAction(action)
  })

  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 && setupState) {
    return (
      <main className="dual-bottom-display setup-bottom-display">
        <header className="dual-controls-header">
          <div>
            <p className="eyebrow">{setupState.contentType}</p>
            <h1>{setupState.title}</h1>
            <small>{setupState.subtitle}</small>
          </div>
          <div className="dual-controls-progress">
            <span>{setupState.difficultyName}</span>
            <span>iLvl {setupState.itemLevel}</span>
            <span>{setupState.experience} XP</span>
          </div>
        </header>
        <section className="setup-bottom-summary">
          <span className={`dungeon-art ${setupState.contentType === 'raid' ? 'raid-art' : ''}`}>
            {setupState.initials}
          </span>
          <div>
            <p className="eyebrow">Selected Run</p>
            <h2>{setupState.title}</h2>
            <p>{setupState.description}</p>
            {setupState.lockedReason && <small>{setupState.lockedReason}</small>}
          </div>
        </section>
        <dl className="setup-bottom-stats">
          <div><dt>Health</dt><dd>{setupState.stats.health}</dd></div>
          <div><dt>Damage</dt><dd>{setupState.stats.damage}</dd></div>
          <div><dt>XP</dt><dd>{setupState.stats.xp}</dd></div>
          <div><dt>Loot</dt><dd>{setupState.stats.loot}</dd></div>
        </dl>
      </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 ${state.opponentParty ? 'pvp-opponent-bottom-display' : ''}`}
      data-combat-active={state.status === 'playing' && !state.paused ? 'true' : 'false'}
    >
      <header className="dual-controls-header">
        <div>
          <p className="eyebrow">{state.opponentParty ? 'Opponent View' : `${state.difficultyName} ${state.contentName}`}</p>
          <h1>{state.opponentParty ? state.opponentName : state.dungeonName}</h1>
          {state.opponentParty && <small>{state.opponentClassName}</small>}
        </div>
        <div className="dual-controls-progress">
          <span>{state.stadium ? `Round ${state.stadium.roundIndex} | ${state.stadium.playerWins}-${state.stadium.opponentWins}` : state.opponentParty ? 'Live PvP' : `Encounter ${state.encounterIndex + 1}/${state.encounterCount}`}</span>
        </div>
      </header>

      {state.opponentParty ? (
        <>
          {state.stadium ? (
            <section className="dual-opponent-progress stadium">
              <div>
                <p className="eyebrow">Dampening</p>
                <strong>{state.stadium.dampeningPercent}%</strong>
              </div>
              <div>
                <p className="eyebrow">Survival</p>
                <strong>{formatDualTime(state.stadium.opponentSurvivalSeconds)}</strong>
              </div>
            </section>
          ) : (
            <section className="dual-opponent-progress">
              <div>
                <p className="eyebrow">Opponent Clear</p>
                <strong>{Math.max(0, Math.floor(state.opponentEnemyHealth ?? 0))} / {state.encounterMaxHealth}</strong>
              </div>
              <div className="bar enemy-health boss-bar">
                <span style={barFillStyle(((state.opponentEnemyHealth ?? 0) / state.encounterMaxHealth) * 100)} />
              </div>
              {typeof state.opponentResource === 'number' && state.opponentMaxResource ? (
                <>
                  <div>
                    <p className="eyebrow">{state.opponentResourceName ?? state.resourceName}</p>
                    <strong>{Math.floor(state.opponentResource)} / {state.opponentMaxResource}</strong>
                  </div>
                  <div className="bar mana-bar">
                    <span style={barFillStyle((state.opponentResource / state.opponentMaxResource) * 100)} />
                  </div>
                </>
              ) : null}
            </section>
          )}

          {state.opponentArena && (
            <section className="dual-opponent-arena" aria-label="Opponent arena">
              <div
                className="dual-opponent-arena-field"
                style={{ aspectRatio: `${state.opponentArena.bounds.width} / ${state.opponentArena.bounds.height}` }}
              >
                {state.opponentArena.bosses.map((boss) => (
                  <div
                    className="dual-opponent-arena-entity boss"
                    key={boss.id}
                    style={{
                      backgroundColor: boss.color,
                      height: `${Math.max(8, (boss.radius / state.opponentArena!.bounds.height) * 100)}%`,
                      left: `${(boss.x / state.opponentArena!.bounds.width) * 100}%`,
                      top: `${(boss.y / state.opponentArena!.bounds.height) * 100}%`,
                      width: `${Math.max(8, (boss.radius / state.opponentArena!.bounds.width) * 100)}%`,
                    }}
                    title={`${boss.name} ${Math.ceil(boss.health)} / ${boss.maxHealth}`}
                  >
                    {boss.icon}
                  </div>
                ))}
                {state.opponentArena.party.map((member) => (
                  <div
                    className={`dual-opponent-arena-entity party ${member.isHealer ? 'healer' : ''}`}
                    key={member.id}
                    style={{
                      backgroundColor: member.color,
                      height: `${Math.max(5, (member.radius / state.opponentArena!.bounds.height) * 100)}%`,
                      left: `${(member.x / state.opponentArena!.bounds.width) * 100}%`,
                      opacity: member.health > 0 ? 1 : 0.35,
                      top: `${(member.y / state.opponentArena!.bounds.height) * 100}%`,
                      width: `${Math.max(5, (member.radius / state.opponentArena!.bounds.width) * 100)}%`,
                    }}
                  >
                    {member.icon}
                  </div>
                ))}
              </div>
            </section>
          )}

          <section className={`dual-opponent-party-grid ${state.opponentParty.length > 6 ? 'raid' : ''}`}>
            {state.opponentParty.map((member) => (
              <PartyMemberFrame
                as="article"
                baseClassName="dual-opponent-member"
                effectMode="compact"
                key={member.id}
                member={member}
              />
            ))}
          </section>

          <section className="dual-opponent-effects">
            <span>Buffs: {state.opponentBuffSummary || 'none'}</span>
            <span>Debuffs: {state.opponentDebuffSummary || 'none'}</span>
          </section>
        </>
      ) : (
        <>

      <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={barFillStyle((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,
  onCastSpell,
}: {
  state: DualScreenCombatState
  onSelectTarget: (id: string) => void
  onCastSpell?: (spell: Spell) => void
}) {
  const enemyPercent = Math.max(
    0,
    (state.encounterHealth / state.encounterMaxHealth) * 100,
  )
  const floatingTextsByMember = useMemo(
    () => groupFloatingTextsByMember(state.floatingTexts),
    [state.floatingTexts],
  )

  const spellButtons = state.spells.map((spell, slotIndex) => {
    if (!spell) return <div className="dual-top-spell empty" key={`empty-${slotIndex}`} />
    const percent = spell.remaining > 0
      ? Math.min(100, (spell.remaining / Math.max(1, spell.cooldown)) * 100)
      : 0
    return (
      <button
        className="dual-top-spell"
        data-controller-nav={state.opponentParty ? 'skip' : undefined}
        disabled={
          !state.playerIsAlive
          || state.resource < spell.cost
          || spell.remaining > 0
          || state.status !== 'playing'
          || state.paused
        }
        key={spell.id}
        onClick={() => onCastSpell?.(spell)}
        type="button"
      >
        <span className={`spell-icon spell-${spell.kind}`}>{spell.glyph}</span>
        {spell.remaining > 0 && <i style={{ height: `${percent}%` }} />}
        {spell.remaining > 0 && <small>{spell.remaining.toFixed(0)}</small>}
      </button>
    )
  })

  return (
    <div className={`dual-top-main ${state.opponentParty ? 'pvp-roguelike-dual-top' : ''}`}>
      <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={barFillStyle(enemyPercent)} />
          </div>
          {state.opponentParty && (
            <div className="dual-top-resource">
              <strong>{state.resourceName} {Math.floor(state.resource)} / {state.maxResource}</strong>
              <div className="bar mana-bar">
                <span style={barFillStyle((state.resource / state.maxResource) * 100)} />
              </div>
            </div>
          )}
          {!state.opponentParty && <p>{state.encounterDescription}</p>}
        </div>
        {state.opponentParty && (
          <div className="dual-top-side-status">
            <div className="dual-top-cooldowns" aria-label="Cooldowns">
              {spellButtons}
            </div>
          </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 (
              <PartyMemberFrame
                baseClassName="dual-top-member"
                controllerIconStyle={state.controllerIconStyle}
                effectMode="compact"
                floatingTexts={floatingTextsByMember.get(member.id) ?? []}
                key={member.id}
                member={member}
                onSelect={onSelectTarget}
                selected={state.selectedId === member.id}
                targetBinding={targetBinding}
              />
            )
          })}
        </div>
      </section>

      {!state.opponentParty && (
        <section className="dual-top-spell-strip">
          {spellButtons}
          <div className="dual-top-resource">
            <strong>{state.resourceName} {Math.floor(state.resource)} / {state.maxResource}</strong>
            <div className="bar mana-bar">
              <span style={barFillStyle((state.resource / state.maxResource) * 100)} />
            </div>
          </div>
        </section>
      )}

    </div>
  )
}
