import { define } from '@because/forge'
import type { App, LogLine as LogLineType } from '../../shared/types'
import { ansiToHtml, stripAnsi } from '../ansi'
import { getLogDates, getLogsForDate } from '../api'
import { isNarrow } from '../state'
import { LogLine, LogsContainer, LogsHeader, LogTime, Section, SectionTitle } from '../styles'
import { theme } from '../themes'
import { update } from '../update'

type LogsState = {
  dates: string[]
  historicalLogs: string[]
  liveLogs: LogLineType[]
  loadingDates: boolean
  loadingLogs: boolean
  searchFilter: string
  selectedDate: string
}

const MAX_LOGS = 100

const logsState = new Map<string, LogsState>()

let currentApp: App | null = null
let _logSource: EventSource | null = null
let _logSourceApp: string | null = null
let _logRenderQueued = false

const getState = (appName: string): LogsState => {
  if (!logsState.has(appName)) {
    logsState.set(appName, {
      dates: [],
      historicalLogs: [],
      liveLogs: [],
      loadingDates: false,
      loadingLogs: false,
      searchFilter: '',
      selectedDate: 'live',
    })
  }
  return logsState.get(appName)!
}

function connectLogStream(appName: string) {
  if (_logSource && _logSourceApp === appName) return
  disconnectLogStream()

  _logSourceApp = appName
  const state = getState(appName)
  state.liveLogs = []

  _logSource = new EventSource(`/api/apps/${appName}/logs/stream`)
  _logSource.onmessage = e => {
    try {
      const line = JSON.parse(e.data) as LogLineType
      state.liveLogs = [...state.liveLogs.slice(-(MAX_LOGS - 1)), line]
      // Debounce log renders to avoid overwhelming the browser
      if (!_logRenderQueued) {
        _logRenderQueued = true
        requestAnimationFrame(() => {
          _logRenderQueued = false
          updateLogsContent()
        })
      }
    } catch {}
  }
  _logSource.onerror = () => {
    if (_logSource?.readyState === EventSource.CLOSED) {
      _logSource = null
      // Reconnect after a brief delay
      setTimeout(() => {
        if (_logSourceApp === appName) connectLogStream(appName)
      }, 2000)
    }
  }
}

export function disconnectLogStream() {
  _logSource?.close()
  _logSource = null
  _logSourceApp = null
}

const LogsControls = define('LogsControls', {
  display: 'flex',
  alignItems: 'center',
  gap: 8,
})

const SmallSelect = define('SmallSelect', {
  base: 'select',
  padding: '4px 8px',
  background: theme('colors-bgSubtle'),
  border: `1px solid ${theme('colors-border')}`,
  borderRadius: '4px',
  color: theme('colors-text'),
  fontSize: 12,
  cursor: 'pointer',
  selectors: {
    '&:focus': {
      outline: 'none',
      borderColor: theme('colors-primary'),
    },
  },
})

const SmallInput = define('SmallInput', {
  base: 'input',
  padding: '4px 8px',
  background: theme('colors-bgSubtle'),
  border: `1px solid ${theme('colors-border')}`,
  borderRadius: '4px',
  color: theme('colors-text'),
  fontSize: 12,
  width: 120,
  selectors: {
    '&:focus': {
      outline: 'none',
      borderColor: theme('colors-primary'),
    },
    '&::placeholder': {
      color: theme('colors-textFaint'),
    },
  },
})

function filterLogs<T extends string | LogLineType>(
  logs: T[],
  filter: string,
  getText: (log: T) => string
): T[] {
  if (!filter) return logs
  const lower = filter.toLowerCase()
  return logs.filter(log => getText(log).toLowerCase().includes(lower))
}

function LogsContent() {
  if (!currentApp) return null

  const state = getState(currentApp.name)
  const isLive = state.selectedDate === 'live'
  const filteredLiveLogs = filterLogs(state.liveLogs, state.searchFilter, l => stripAnsi(l.text))
  const filteredHistoricalLogs = filterLogs(state.historicalLogs, state.searchFilter, l => l)

  return (
    <>
      {state.loadingLogs && (
        <LogLine>
          <LogTime>--:--:--</LogTime>
          <span style={{ color: theme('colors-textFaint') }}>Loading...</span>
        </LogLine>
      )}
      {!state.loadingLogs && isLive && (
        filteredLiveLogs.length ? (
          filteredLiveLogs.map((line, i) => (
            <LogLine key={i}>
              <LogTime>{new Date(line.time).toLocaleTimeString()}</LogTime>
              <span dangerouslySetInnerHTML={{ __html: ansiToHtml(line.text) }} />
            </LogLine>
          ))
        ) : (
          <LogLine>
            <LogTime>--:--:--</LogTime>
            <span style={{ color: theme('colors-textFaint') }}>
              {state.searchFilter ? 'No matching logs' : 'No logs yet'}
            </span>
          </LogLine>
        )
      )}
      {!state.loadingLogs && !isLive && (
        filteredHistoricalLogs.length ? (
          filteredHistoricalLogs.map((line, i) => (
            <LogLine key={i}>
              <span style={{ color: theme('colors-textFaintest'), marginRight: 12 }}>
                {line.match(/^\[([^\]]+)\]/)?.[1]?.split('T')[1]?.slice(0, 8) ?? '--:--:--'}
              </span>
              <span dangerouslySetInnerHTML={{ __html: ansiToHtml(line.replace(/^\[[^\]]+\] \[[^\]]+\] /, '')) }} />
            </LogLine>
          ))
        ) : (
          <LogLine>
            <LogTime>--:--:--</LogTime>
            <span style={{ color: theme('colors-textFaint') }}>
              {state.searchFilter ? 'No matching logs' : 'No logs for this date'}
            </span>
          </LogLine>
        )
      )}
    </>
  )
}

const updateLogsContent = () =>
  update('#logs-content', <LogsContent />)

async function loadDates(appName: string) {
  const state = getState(appName)
  if (state.loadingDates || state.dates.length > 0) return

  state.loadingDates = true

  try {
    const dates = await getLogDates(appName)
    state.dates = dates
  } catch (e) {
    console.error('Failed to load log dates:', e)
  } finally {
    state.loadingDates = false
  }
}

async function loadHistoricalLogs(appName: string, date: string) {
  const state = getState(appName)
  state.loadingLogs = true
  updateLogsContent()

  try {
    const logs = await getLogsForDate(appName, date)
    state.historicalLogs = logs
  } catch (e) {
    console.error('Failed to load logs:', e)
    state.historicalLogs = []
  } finally {
    state.loadingLogs = false
    updateLogsContent()
  }
}

function handleDateChange(appName: string, date: string) {
  const state = getState(appName)
  state.selectedDate = date
  state.historicalLogs = []

  if (date !== 'live') {
    loadHistoricalLogs(appName, date)
  } else {
    updateLogsContent()
  }
}

function handleSearchChange(appName: string, value: string) {
  const state = getState(appName)
  state.searchFilter = value
  updateLogsContent()
}

export function LogsSection({ app }: { app: App }) {
  currentApp = app
  const state = getState(app.name)

  // Connect to per-app log stream for live logs
  connectLogStream(app.name)

  // Load dates on first render
  if (state.dates.length === 0 && !state.loadingDates) {
    loadDates(app.name)
  }

  return (
    <Section style={{ flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0, marginBottom: 0 }}>
      <LogsHeader>
        <SectionTitle style={{ marginBottom: 0 }}>Logs</SectionTitle>
        <LogsControls>
          <SmallInput
            id="logs-filter"
            type="text"
            placeholder="Filter..."
            value={state.searchFilter}
            onInput={(e: Event) => handleSearchChange(app.name, (e.target as HTMLInputElement).value)}
          />
          <SmallSelect
            id="logs-date"
            value={state.selectedDate}
            onChange={(e: Event) => handleDateChange(app.name, (e.target as HTMLSelectElement).value)}
          >
            <option value="live">Live</option>
            {state.dates.map(date => (
              <option key={date} value={date}>{date}</option>
            ))}
          </SmallSelect>
        </LogsControls>
      </LogsHeader>
      <LogsContainer id="logs-content" narrow={isNarrow || undefined}>
        <LogsContent />
      </LogsContainer>
    </Section>
  )
}
