import React, { useState, useMemo, useCallback, useEffect } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { Eye, RefreshCw, LineChart, X } from 'lucide-react'
import Plot from 'react-plotly.js'
import api from '../api'

// Format Date as YYYY-MM-DD using local time (avoids UTC/DST shift from toISOString)
const fmtDate = (d: Date) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`

// Booking.com search results for the scrape location on a given night
const buildSearchUrl = (location: string, checkin: string) => {
  const co = new Date(checkin + 'T00:00:00')
  co.setDate(co.getDate() + 1)
  const params = new URLSearchParams({
    ss: location,
    checkin,
    checkout: fmtDate(co),
    group_adults: '2',
    no_rooms: '1',
    group_children: '0',
  })
  return `https://www.booking.com/searchresults.html?${params}`
}

// ============================================
// TYPES
// ============================================

interface ScrapeJob {
  from: string
  to: string
}

interface ScraperStatus {
  enabled: boolean
  backend: string
  location_configured: boolean
  location_name: string | null
  last_scrape: {
    batch_id: string
    scrape_type: string
    started_at: string | null
    completed_at: string | null
    status: string
    hotels_found: number | null
    rates_scraped: number | null
    error_message: string | null
  } | null
}

interface Hotel {
  id: number
  booking_com_id: string
  name: string
  booking_com_url: string | null
  star_rating: number | null
  review_score: number | null
  review_count: number | null
  tier: 'own' | 'competitor' | 'market'
  display_order: number
  notes: string | null
  first_seen_at: string | null
  last_seen_at: string | null
  direct_hotel_id?: number | null
}

interface RateMatrixResponse {
  from_date: string
  to_date: string
  dates: string[]
  last_scraped?: Record<string, string | null>
  hotels: {
    id: number
    name: string
    tier: string
    display_order: number
    star_rating: number | null
    review_score: number | null
    booking_com_url: string | null
    direct_hotel_id?: number | null
  }[]
  rates: Record<number, Record<string, {
    availability_status: string
    rate_gross: number | null
    last_available_rate: number | null
    room_type: string | null
    breakfast_included: boolean | null
    free_cancellation: boolean | null
    no_prepayment: boolean | null
    rooms_left: number | null
    scraped_at: string | null
  }>>
}

interface ScheduleInfo {
  daily_time: string
  today: string
  weekday: string
  tiers: {
    high: { description: string; dates_today: number; range: string | null }
    medium: { description: string; dates_today: number; range: string | null }
    low: { description: string; dates_today: number; range: string | null }
  }
  total_dates_today: number
}

interface QueueStatus {
  statuses: Record<string, { count: number; earliest: string | null; latest: string | null }>
  retries_pending: number
  total_pending: number
  total_completed: number
  total_failed: number
}

interface CoverageEntry {
  date: string
  tier: 'high' | 'medium' | 'low' | 'none'
  last_scraped: string | null
  next_expected: string | null
}

interface CoverageResponse {
  today: string
  coverage: CoverageEntry[]
}

interface ScrapeHistoryEntry {
  batch_id: string
  scrape_type: string
  started_at: string | null
  completed_at: string | null
  status: string
  dates_queued: number | null
  dates_completed: number | null
  dates_failed: number | null
  hotels_found: number | null
  rates_scraped: number | null
  error_message: string | null
  blocked_at: string | null
  resume_after: string | null
}

// ============================================
// HELPERS
// ============================================

const formatCurrency = (value: number | null): string => {
  if (value === null || value === undefined) return '-'
  return new Intl.NumberFormat('en-GB', {
    style: 'currency',
    currency: 'GBP',
    minimumFractionDigits: 0,
    maximumFractionDigits: 0,
  }).format(value)
}

const formatDateShort = (dateStr: string): string => {
  const date = new Date(dateStr + 'T00:00:00')
  return date.toLocaleDateString('en-GB', { day: 'numeric' })
}

const formatDayOfWeek = (dateStr: string): string => {
  const date = new Date(dateStr + 'T00:00:00')
  return date.toLocaleDateString('en-GB', { weekday: 'short' })
}

const isWeekend = (dateStr: string): boolean => {
  const date = new Date(dateStr + 'T00:00:00')
  const day = date.getDay()
  return day === 0 || day === 6
}

const formatDateTime = (iso: string | null): string => {
  if (!iso) return '-'
  const d = new Date(iso)
  return d.toLocaleString('en-GB', { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' })
}

const formatScrapeAge = (iso: string | null): string => {
  if (!iso) return ''
  const scraped = new Date(iso)
  const now = new Date()
  const diffMs = now.getTime() - scraped.getTime()
  const diffMins = Math.floor(diffMs / 60000)
  if (diffMins < 60) return `${diffMins}m ago`
  const diffHours = Math.floor(diffMins / 60)
  if (diffHours < 24) return `${diffHours}h ago`
  const diffDays = Math.floor(diffHours / 24)
  return `${diffDays}d ago`
}

const tierColor = (tier: string) => {
  switch (tier) {
    case 'own': return '#2563eb'
    case 'competitor': return '#d97706'
    case 'market': return '#64748b'
    default: return '#64748b'
  }
}

// ============================================
// INLINE STYLE HELPERS (replacing theme utilities)
// ============================================

const mergeStyles = (...s: React.CSSProperties[]): React.CSSProperties =>
  Object.assign({}, ...s)

const buttonStyle = (variant: 'primary' | 'secondary' | 'outline', size?: 'small'): React.CSSProperties => {
  const base: React.CSSProperties = {
    border: 'none',
    borderRadius: '6px',
    cursor: 'pointer',
    fontWeight: 500,
    padding: size === 'small' ? '4px 10px' : '8px 16px',
    fontSize: size === 'small' ? '13px' : '14px',
    lineHeight: 1.4,
    transition: 'all 0.15s',
  }
  if (variant === 'primary') return { ...base, background: 'var(--gold)', color: '#fff' }
  if (variant === 'secondary') return { ...base, background: 'var(--navy)', color: '#fff' }
  return { ...base, background: 'transparent', color: 'var(--text-dark)', border: '1px solid var(--card-border)' }
}

const badgeStyle = (variant: 'success' | 'error' | 'warning' | 'info'): React.CSSProperties => {
  const map: Record<string, React.CSSProperties> = {
    success: { background: '#dcfce7', color: '#16a34a', padding: '2px 8px', borderRadius: '99px', fontSize: '12px', fontWeight: 600 },
    error:   { background: '#fee2e2', color: '#dc2626', padding: '2px 8px', borderRadius: '99px', fontSize: '12px', fontWeight: 600 },
    warning: { background: '#fef3c7', color: '#d97706', padding: '2px 8px', borderRadius: '99px', fontSize: '12px', fontWeight: 600 },
    info:    { background: '#dbeafe', color: '#2563eb', padding: '2px 8px', borderRadius: '99px', fontSize: '12px', fontWeight: 600 },
  }
  return map[variant] || map.info
}

const inputStyle: React.CSSProperties = {
  width: '100%',
  padding: '8px 10px',
  borderRadius: '6px',
  border: '1px solid var(--card-border)',
  fontSize: '14px',
  color: 'var(--text-dark)',
  background: 'var(--card-bg)',
  boxSizing: 'border-box',
}

const inputLabelStyle: React.CSSProperties = {
  display: 'block',
  fontSize: '12px',
  fontWeight: 600,
  color: 'var(--text-mid)',
  marginBottom: '4px',
  textTransform: 'uppercase',
  letterSpacing: '0.04em',
}

// ============================================
// TABS
// ============================================

type TabId = 'matrix' | 'hotels' | 'parity' | 'settings'

// ============================================
// STATUS PANEL
// ============================================

const StatusPanel: React.FC<{ status: ScraperStatus | undefined, isLoading: boolean }> = ({ status, isLoading }) => {
  if (isLoading) return <div style={styles.statusBar}>Loading status...</div>
  if (!status) return null

  return (
    <div style={styles.statusBar}>
      <div style={styles.statusItem}>
        <span style={styles.statusLabel}>Scraper</span>
        <span style={badgeStyle(status.enabled ? 'success' : 'error')}>
          {status.enabled ? 'Enabled' : 'Disabled'}
        </span>
      </div>
      <div style={styles.statusItem}>
        <span style={styles.statusLabel}>Location</span>
        <span style={{ fontSize: '13px', color: status.location_configured ? 'var(--text-dark)' : 'var(--text-mid)' }}>
          {status.location_name || 'Not configured'}
        </span>
      </div>
      <div style={styles.statusItem}>
        <span style={styles.statusLabel}>Backend</span>
        <span style={{ fontSize: '11px', color: 'var(--text-mid)' }}>{status.backend}</span>
      </div>
      {status.last_scrape && (
        <div style={styles.statusItem}>
          <span style={styles.statusLabel}>Last Scrape</span>
          <span style={badgeStyle(
            status.last_scrape.status === 'completed' ? 'success' :
            status.last_scrape.status === 'blocked' ? 'warning' :
            status.last_scrape.status === 'running' ? 'info' : 'error'
          )}>
            {status.last_scrape.status}
          </span>
          <span style={{ fontSize: '11px', color: 'var(--text-mid)' }}>
            {formatDateTime(status.last_scrape.completed_at || status.last_scrape.started_at)}
            {status.last_scrape.hotels_found ? ` | ${status.last_scrape.hotels_found} hotels, ${status.last_scrape.rates_scraped} rates` : ''}
          </span>
        </div>
      )}
    </div>
  )
}

// ============================================
// SETTINGS TAB
// ============================================

const SettingsTab: React.FC = () => {
  const queryClient = useQueryClient()
  const [locationName, setLocationName] = useState('')
  const [searchUrl, setSearchUrl] = useState('')
  const [pages, setPages] = useState(1)
  const [adults, setAdults] = useState(2)
  const [scrapeFrom, setScrapeFrom] = useState(() => fmtDate(new Date()))
  const [scrapeTo, setScrapeTo] = useState(() => {
    const d = new Date()
    d.setDate(d.getDate() + 7)
    return fmtDate(d)
  })

  const { data: status } = useQuery<ScraperStatus>({
    queryKey: ['scraper-status'],
    queryFn: async () => (await api.get('/competitors/status')).data,
  })

  const { data: history } = useQuery<ScrapeHistoryEntry[]>({
    queryKey: ['scrape-history'],
    queryFn: async () => (await api.get('/competitors/scrape-history?limit=10')).data,
  })

  const { data: scheduleInfo } = useQuery<ScheduleInfo>({
    queryKey: ['schedule-info'],
    queryFn: async () => (await api.get('/competitors/schedule-info')).data,
  })

  const { data: queueStatus } = useQuery<QueueStatus>({
    queryKey: ['queue-status'],
    queryFn: async () => (await api.get('/competitors/queue-status')).data,
    refetchInterval: 30000,
  })

  const { data: coverage } = useQuery<CoverageResponse>({
    queryKey: ['scrape-coverage'],
    queryFn: async () => (await api.get('/competitors/scrape-coverage')).data,
    staleTime: 60000,
  })

  const setLocationMutation = useMutation({
    mutationFn: async () => {
      return (await api.post('/competitors/config/location', {
        location_name: locationName,
        location_search_url: searchUrl || undefined,
        pages_to_scrape: pages,
        adults: adults,
      })).data
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['scraper-status'] })
      setLocationName('')
      setSearchUrl('')
    },
  })

  const enableMutation = useMutation({
    mutationFn: async (enabled: boolean) => {
      return (await api.post(`/competitors/config/enable?enabled=${enabled}`)).data
    },
    onSuccess: () => queryClient.invalidateQueries({ queryKey: ['scraper-status'] }),
  })

  const scrapeMutation = useMutation({
    mutationFn: async () => {
      return (await api.post('/competitors/scrape', {
        from_date: scrapeFrom,
        to_date: scrapeTo,
      })).data
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['scraper-status'] })
      queryClient.invalidateQueries({ queryKey: ['scrape-history'] })
    },
  })

  return (
    <div style={styles.settingsGrid}>
      {/* Location Configuration */}
      <div style={styles.card}>
        <h3 style={styles.cardTitle}>Location Configuration</h3>
        <p style={styles.cardDescription}>
          Set the location for competitor rate scraping.
          {status?.location_name && (
            <> Currently: <strong>{status.location_name}</strong></>
          )}
        </p>
        <div style={styles.formRow}>
          <div style={styles.formGroup}>
            <label style={inputLabelStyle}>Location Name</label>
            <input
              type="text"
              value={locationName}
              onChange={e => setLocationName(e.target.value)}
              placeholder="e.g. Bowness-on-Windermere"
              style={inputStyle}
            />
          </div>
        </div>
        <div style={{ marginTop: '12px' }}>
          <label style={inputLabelStyle}>Booking.com search URL (recommended)</label>
          <input
            type="text"
            value={searchUrl}
            onChange={e => setSearchUrl(e.target.value)}
            placeholder="https://www.booking.com/searchresults.html?ss=…&dest_id=…"
            style={inputStyle}
          />
          <p style={{ fontSize: '12px', color: 'var(--text-mid)', margin: '6px 0 0' }}>
            Search your area on booking.com and paste the address-bar URL here. It pins the
            exact destination (a plain name can resolve to the wrong place). Dates and paging
            are set automatically — only the destination is read from the URL.
          </p>
        </div>
        <div style={{ ...styles.formRow, marginTop: '12px' }}>
          <div style={styles.formGroupSmall}>
            <label style={inputLabelStyle}>Pages</label>
            <input
              type="number"
              value={pages}
              onChange={e => setPages(parseInt(e.target.value) || 2)}
              min={1}
              max={5}
              style={inputStyle}
            />
          </div>
          <div style={styles.formGroupSmall}>
            <label style={inputLabelStyle}>Adults</label>
            <input
              type="number"
              value={adults}
              onChange={e => setAdults(parseInt(e.target.value) || 2)}
              min={1}
              max={4}
              style={inputStyle}
            />
          </div>
        </div>
        <button
          onClick={() => setLocationMutation.mutate()}
          disabled={(!locationName && !searchUrl) || setLocationMutation.isPending}
          style={mergeStyles(
            buttonStyle('primary'),
            { marginTop: '16px', opacity: (!locationName && !searchUrl) ? 0.5 : 1 }
          )}
        >
          {setLocationMutation.isPending ? 'Saving...' : 'Set Location'}
        </button>
        {setLocationMutation.isError && (
          <p style={styles.errorText}>
            {(setLocationMutation.error as any)?.response?.data?.detail || 'Failed to set location'}
          </p>
        )}
      </div>

      {/* Scraper Controls */}
      <div style={styles.card}>
        <h3 style={styles.cardTitle}>Scraper Controls</h3>
        <div style={styles.controlRow}>
          <button
            onClick={() => enableMutation.mutate(!status?.enabled)}
            style={buttonStyle(status?.enabled ? 'outline' : 'secondary')}
          >
            {status?.enabled ? 'Disable Scraper' : 'Enable Scraper'}
          </button>
        </div>
      </div>

      {/* Schedule Info */}
      <div style={styles.card}>
        <h3 style={styles.cardTitle}>Automatic Schedule</h3>
        {scheduleInfo ? (
          <div>
            <p style={styles.cardDescription}>
              Runs daily at <strong>{scheduleInfo.daily_time}</strong> ({scheduleInfo.weekday})
            </p>
            <div style={styles.scheduleGrid}>
              {Object.entries(scheduleInfo.tiers).map(([key, tier]) => (
                <div key={key} style={styles.scheduleTier}>
                  <div style={styles.scheduleTierHeader}>
                    <span style={styles.scheduleTierName}>{key}</span>
                    <span style={badgeStyle(tier.dates_today > 0 ? 'info' : 'warning')}>
                      {tier.dates_today} dates
                    </span>
                  </div>
                  <p style={styles.scheduleTierDesc}>{tier.description}</p>
                  {tier.range && (
                    <p style={styles.scheduleTierRange}>{tier.range}</p>
                  )}
                </div>
              ))}
            </div>
            <p style={{ fontSize: '13px', color: 'var(--text-dark)', marginTop: '16px', fontWeight: 600 }}>
              Total today: {scheduleInfo.total_dates_today} dates
            </p>
          </div>
        ) : (
          <p style={styles.noData}>Loading schedule...</p>
        )}

        {/* Queue Status */}
        {queueStatus && (queueStatus.total_pending > 0 || queueStatus.total_failed > 0) && (
          <div style={mergeStyles(styles.queuePanel, { marginTop: '16px' })}>
            <h4 style={{ margin: '0 0 8px', fontSize: '13px', color: 'var(--text-dark)' }}>Queue</h4>
            <div style={styles.queueStats}>
              {queueStatus.total_pending > 0 && (
                <span style={badgeStyle('info')}>{queueStatus.total_pending} pending</span>
              )}
              {queueStatus.retries_pending > 0 && (
                <span style={badgeStyle('warning')}>{queueStatus.retries_pending} retries</span>
              )}
              {queueStatus.total_completed > 0 && (
                <span style={badgeStyle('success')}>{queueStatus.total_completed} done</span>
              )}
              {queueStatus.total_failed > 0 && (
                <span style={badgeStyle('error')}>{queueStatus.total_failed} failed</span>
              )}
            </div>
          </div>
        )}
      </div>

      {/* Manual Scrape */}
      <div style={styles.card}>
        <h3 style={styles.cardTitle}>Manual Scrape</h3>
        <p style={styles.cardDescription}>
          Trigger a one-off scrape for a date range. Runs in background.
        </p>
        <div style={styles.formRow}>
          <div style={styles.formGroup}>
            <label style={inputLabelStyle}>From Date</label>
            <input
              type="date"
              value={scrapeFrom}
              onChange={e => setScrapeFrom(e.target.value)}
              style={inputStyle}
            />
          </div>
          <div style={styles.formGroup}>
            <label style={inputLabelStyle}>To Date</label>
            <input
              type="date"
              value={scrapeTo}
              onChange={e => setScrapeTo(e.target.value)}
              style={inputStyle}
            />
          </div>
        </div>
        <button
          onClick={() => scrapeMutation.mutate()}
          disabled={scrapeMutation.isPending || !status?.location_configured}
          style={mergeStyles(
            buttonStyle('primary'),
            { marginTop: '16px', opacity: (!status?.location_configured) ? 0.5 : 1 }
          )}
        >
          {scrapeMutation.isPending ? 'Starting...' : 'Start Scrape'}
        </button>
        {!status?.location_configured && (
          <p style={styles.hintText}>Configure a location first</p>
        )}
        {scrapeMutation.isSuccess && (
          <p style={{ color: 'var(--success)', fontSize: '13px', marginTop: '8px' }}>
            Scrape started! Check status for progress.
          </p>
        )}
        {scrapeMutation.isError && (
          <p style={styles.errorText}>
            {(scrapeMutation.error as any)?.response?.data?.detail || 'Failed to start scrape'}
          </p>
        )}
      </div>

      {/* Scrape History */}
      <div style={mergeStyles(styles.card, { gridColumn: '1 / -1' })}>
        <h3 style={styles.cardTitle}>Scrape History</h3>
        {history && history.length > 0 ? (
          <div style={styles.historyTable}>
            <table style={styles.table}>
              <thead>
                <tr>
                  <th style={styles.th}>Type</th>
                  <th style={styles.th}>Started</th>
                  <th style={styles.th}>Status</th>
                  <th style={styles.th}>Hotels</th>
                  <th style={styles.th}>Rates</th>
                  <th style={styles.th}>Error</th>
                </tr>
              </thead>
              <tbody>
                {history.map(entry => (
                  <tr key={entry.batch_id}>
                    <td style={styles.td}>{entry.scrape_type}</td>
                    <td style={styles.td}>{formatDateTime(entry.started_at)}</td>
                    <td style={styles.td}>
                      <span style={badgeStyle(
                        entry.status === 'completed' ? 'success' :
                        entry.status === 'blocked' ? 'warning' :
                        entry.status === 'running' ? 'info' : 'error'
                      )}>
                        {entry.status}
                      </span>
                    </td>
                    <td style={styles.td}>{entry.hotels_found ?? '-'}</td>
                    <td style={styles.td}>{entry.rates_scraped ?? '-'}</td>
                    <td style={mergeStyles(styles.td, { maxWidth: '200px', overflow: 'hidden', textOverflow: 'ellipsis' })}>
                      {entry.error_message || '-'}
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        ) : (
          <p style={styles.noData}>No scrape history yet</p>
        )}
      </div>

      {/* Scrape Coverage - 365 day view */}
      <div style={mergeStyles(styles.card, { gridColumn: '1 / -1' })}>
        <h3 style={styles.cardTitle}>Scrape Coverage (365 days)</h3>
        <p style={styles.cardDescription}>
          Each cell is a date. Color shows freshness of data; letter shows priority (H=high, M=medium, L=low).
        </p>
        {coverage ? <CoverageGrid coverage={coverage} /> : <p style={styles.noData}>Loading coverage...</p>}
      </div>
    </div>
  )
}

// ============================================
// COVERAGE GRID
// ============================================

const freshnessColor = (lastScraped: string | null): React.CSSProperties => {
  if (!lastScraped) return { background: '#e8e8e8', color: '#64748b' }
  const hours = (Date.now() - new Date(lastScraped).getTime()) / 3600000
  if (hours < 24) return { background: '#c6efce', color: '#1a7a2e' }       // green - fresh
  if (hours < 72) return { background: '#fff3cd', color: '#856404' }       // yellow - 1-3 days
  if (hours < 168) return { background: '#ffe0b2', color: '#e65100' }      // orange - 3-7 days
  if (hours < 336) return { background: '#f8d7da', color: '#721c24' }      // red - 7-14 days
  return { background: '#c62828', color: '#ffffff' }                        // dark red - >14 days
}

const tierLabel = (tier: string) => {
  switch (tier) {
    case 'high': return 'H'
    case 'medium': return 'M'
    case 'low': return 'L'
    default: return '-'
  }
}

const CoverageGrid: React.FC<{ coverage: CoverageResponse }> = ({ coverage }) => {
  // Group by month
  const months = useMemo(() => {
    const grouped: Record<string, CoverageEntry[]> = {}
    for (const entry of coverage.coverage) {
      const monthKey = entry.date.substring(0, 7) // YYYY-MM
      if (!grouped[monthKey]) grouped[monthKey] = []
      grouped[monthKey].push(entry)
    }
    return Object.entries(grouped)
  }, [coverage.coverage])

  const formatMonthLabel = (monthKey: string) => {
    const [y, m] = monthKey.split('-')
    const d = new Date(parseInt(y), parseInt(m) - 1, 1)
    return d.toLocaleDateString('en-GB', { month: 'short', year: 'numeric' })
  }

  const formatDateLabel = (dateStr: string) => {
    const d = new Date(dateStr + 'T00:00:00')
    return d.toLocaleDateString('en-GB', { weekday: 'short', day: 'numeric' })
  }

  const formatAge = (iso: string | null): string => {
    if (!iso) return 'Never scraped'
    const hours = (Date.now() - new Date(iso).getTime()) / 3600000
    if (hours < 1) return `${Math.floor(hours * 60)}m ago`
    if (hours < 24) return `${Math.floor(hours)}h ago`
    return `${Math.floor(hours / 24)}d ago`
  }

  return (
    <div style={styles.coverageContainer}>
      {/* Legend */}
      <div style={styles.coverageLegend}>
        <span style={mergeStyles(styles.coverageLegendItem, { background: '#c6efce' })}>{'<'}24h</span>
        <span style={mergeStyles(styles.coverageLegendItem, { background: '#fff3cd' })}>1-3d</span>
        <span style={mergeStyles(styles.coverageLegendItem, { background: '#ffe0b2' })}>3-7d</span>
        <span style={mergeStyles(styles.coverageLegendItem, { background: '#f8d7da' })}>7-14d</span>
        <span style={mergeStyles(styles.coverageLegendItem, { background: '#c62828', color: '#fff' })}>{'>'} 14d</span>
        <span style={mergeStyles(styles.coverageLegendItem, { background: '#e8e8e8' })}>Never</span>
        <span style={{ marginLeft: '16px', fontSize: '11px', color: 'var(--text-mid)' }}>
          H=High M=Medium L=Low priority
        </span>
      </div>
      {months.map(([monthKey, entries]) => (
        <div key={monthKey} style={styles.coverageMonth}>
          <div style={styles.coverageMonthLabel}>{formatMonthLabel(monthKey)}</div>
          <div style={styles.coverageCells}>
            {entries.map(entry => (
              <div
                key={entry.date}
                style={mergeStyles(styles.coverageCell, freshnessColor(entry.last_scraped))}
                title={`${formatDateLabel(entry.date)}\nTier: ${entry.tier}\nLast: ${formatAge(entry.last_scraped)}\nNext: ${entry.next_expected || 'N/A'}`}
              >
                <span style={styles.coverageCellDay}>
                  {new Date(entry.date + 'T00:00:00').getDate()}
                </span>
                <span style={styles.coverageCellTier}>
                  {tierLabel(entry.tier)}
                </span>
              </div>
            ))}
          </div>
        </div>
      ))}
    </div>
  )
}

// ============================================
// HOTELS TAB
// ============================================

// ============================================
// PARITY ALERTS TAB
// ============================================

interface ParityAlert {
  id: number
  rate_date: string
  room_category: string | null
  newbook_rate: number | null
  booking_com_rate: number | null
  difference_pct: number | null
  alert_type: string
  alert_status: string
  created_at: string | null
  acknowledged_at: string | null
  acknowledged_by: string | null
}

const ParityAlertsTab: React.FC = () => {
  const queryClient = useQueryClient()
  const [statusFilter, setStatusFilter] = useState<string>('active')

  const { data: alerts, isLoading } = useQuery<ParityAlert[]>({
    queryKey: ['parity-alerts', statusFilter],
    queryFn: async () => {
      const params = statusFilter ? `?status=${statusFilter}` : ''
      return (await api.get(`/competitors/parity/alerts${params}`)).data
    },
  })

  const { data: parityConfig } = useQuery<Record<string, string | null>>({
    queryKey: ['system-config'],
    queryFn: async () => (await api.get('/competitors/config/system')).data,
  })

  const ackMutation = useMutation({
    mutationFn: async (alertId: number) =>
      (await api.put(`/competitors/parity/alerts/${alertId}/acknowledge`)).data,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['parity-alerts'] })
      queryClient.invalidateQueries({ queryKey: ['parity-alert-count'] })
    },
  })

  const markupUnit = parityConfig?.['parity_markup_unit'] ?? 'pct'
  const toleranceUnit = parityConfig?.['parity_tolerance_unit'] ?? 'pct'
  const markupVal = parityConfig?.['parity_markup_value'] ?? parityConfig?.['parity_expected_markup_pct'] ?? '0'
  const toleranceVal = parityConfig?.['parity_tolerance_value'] ?? parityConfig?.['parity_tolerance_pct'] ?? '2'
  const markupLabel = markupUnit === 'gbp' ? `£${markupVal}` : `${markupVal}%`
  const toleranceLabel = toleranceUnit === 'gbp' ? `£${toleranceVal}` : `${toleranceVal}%`

  const thStyle: React.CSSProperties = {
    textAlign: 'left', padding: '8px 12px', fontSize: 11, fontWeight: 600,
    color: 'var(--text-mid)', textTransform: 'uppercase', letterSpacing: '0.04em',
    borderBottom: '1px solid var(--border)',
  }
  const tdStyle: React.CSSProperties = {
    padding: '9px 12px', fontSize: 13, borderBottom: '1px solid var(--border)',
  }

  return (
    <div>
      <div style={{ fontSize: 12, color: 'var(--text-mid)', marginBottom: 14, maxWidth: 720 }}>
        Dates where our Booking.com rate deviates from the expected level
        (Newbook rate + {markupLabel} markup, ±{toleranceLabel} tolerance). Checked daily at 06:45 —
        adjust the markup and tolerance in Settings → Rate Parity. Acknowledge a date once
        dealt with; alerts auto-resolve when the rates come back in line.
      </div>

      <div style={{ display: 'flex', gap: 6, marginBottom: 14, alignItems: 'center' }}>
        <span style={{ fontSize: 12, color: 'var(--text-mid)' }}>Status:</span>
        {['active', 'acknowledged', 'resolved', ''].map(s => (
          <button
            key={s}
            onClick={() => setStatusFilter(s)}
            style={mergeStyles(
              buttonStyle(statusFilter === s ? 'secondary' : 'outline', 'small'),
              statusFilter === s ? {} : { opacity: 0.7 }
            )}
          >
            {s || 'All'}
          </button>
        ))}
      </div>

      {isLoading && (
        <div style={styles.loading}><div style={styles.spinner} /><span>Loading alerts...</span></div>
      )}

      {!isLoading && (!alerts || alerts.length === 0) && (
        <div style={styles.emptyState}>
          <h3 style={{ margin: 0, color: 'var(--text-dark)' }}>No {statusFilter || ''} parity alerts</h3>
          <p style={{ color: 'var(--text-mid)', margin: '8px 0 0' }}>
            {statusFilter === 'active'
              ? 'Booking.com is pricing within the expected band of Newbook.'
              : 'Nothing here yet.'}
          </p>
        </div>
      )}

      {!isLoading && alerts && alerts.length > 0 && (
        <div style={{ background: 'var(--white, #fff)', border: '1px solid var(--border)', borderRadius: 10, overflow: 'auto' }}>
          <table style={{ width: '100%', borderCollapse: 'collapse' }}>
            <thead>
              <tr>
                <th style={thStyle}>Date</th>
                <th style={thStyle}>Newbook</th>
                <th style={thStyle}>Booking.com</th>
                <th style={thStyle}>Deviation</th>
                <th style={thStyle}>Room</th>
                <th style={thStyle}>Status</th>
                <th style={thStyle}></th>
              </tr>
            </thead>
            <tbody>
              {alerts.map(a => (
                <tr key={a.id}>
                  <td style={mergeStyles(tdStyle, { fontWeight: 600 })}>{a.rate_date}</td>
                  <td style={tdStyle}>{a.newbook_rate != null ? `£${a.newbook_rate.toFixed(2)}` : '—'}</td>
                  <td style={tdStyle}>{a.booking_com_rate != null ? `£${a.booking_com_rate.toFixed(2)}` : '—'}</td>
                  <td style={tdStyle}>
                    {a.difference_pct != null && (() => {
                      // expected = booking / (1 + dev%); £ deviation derived from that
                      const expected = a.booking_com_rate != null
                        ? a.booking_com_rate / (1 + a.difference_pct / 100)
                        : null
                      const devGbp = expected != null && a.booking_com_rate != null
                        ? a.booking_com_rate - expected
                        : null
                      return (
                        <span style={badgeStyle(a.alert_type === 'higher' ? 'warning' : 'error')}>
                          {a.difference_pct > 0 ? '+' : ''}{a.difference_pct.toFixed(1)}%
                          {devGbp != null ? ` (${devGbp > 0 ? '+' : '−'}£${Math.abs(devGbp).toFixed(2)})` : ''} vs expected
                        </span>
                      )
                    })()}
                  </td>
                  <td style={mergeStyles(tdStyle, { color: 'var(--text-mid)', fontSize: 12 })}>{a.room_category || '—'}</td>
                  <td style={tdStyle}>
                    <span style={badgeStyle(
                      a.alert_status === 'active' ? 'error'
                        : a.alert_status === 'acknowledged' ? 'info' : 'success'
                    )}>
                      {a.alert_status}
                    </span>
                  </td>
                  <td style={tdStyle}>
                    {a.alert_status === 'active' && (
                      <button
                        style={buttonStyle('outline', 'small')}
                        disabled={ackMutation.isPending}
                        onClick={() => ackMutation.mutate(a.id)}
                      >
                        Acknowledge
                      </button>
                    )}
                    {a.alert_status === 'acknowledged' && a.acknowledged_by && (
                      <span style={{ fontSize: 11, color: 'var(--text-mid)' }}>by {a.acknowledged_by}</span>
                    )}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}
    </div>
  )
}

const HotelsTab: React.FC = () => {
  const queryClient = useQueryClient()
  const [tierFilter, setTierFilter] = useState<string>('')

  const { data: hotels, isLoading } = useQuery<Hotel[]>({
    queryKey: ['competitor-hotels', tierFilter],
    queryFn: async () => {
      const params = tierFilter ? `?tier=${tierFilter}` : ''
      return (await api.get(`/competitors/hotels${params}`)).data
    },
  })

  const tierMutation = useMutation({
    mutationFn: async ({ hotelId, tier }: { hotelId: number, tier: string }) => {
      return (await api.put(`/competitors/hotels/${hotelId}/tier`, { tier })).data
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['competitor-hotels'] })
    },
  })

  // Direct booking-engine hotels, for linking (powers the matrix direct-rates sub-row)
  const { data: directHotels } = useQuery<{ id: number; name: string }[]>({
    queryKey: ['direct-hotels-link-options'],
    queryFn: async () => (await api.get('/direct/hotels')).data,
    retry: false,
  })

  const linkMutation = useMutation({
    mutationFn: async ({ hotelId, directId }: { hotelId: number, directId: number | null }) => {
      return (await api.put(`/competitors/hotels/${hotelId}/direct-link`, { direct_hotel_id: directId })).data
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['competitor-hotels'] })
      queryClient.invalidateQueries({ queryKey: ['competitor-matrix'] })
      queryClient.invalidateQueries({ queryKey: ['market-direct-rates'] })
    },
  })

  const grouped = useMemo(() => {
    if (!hotels) return { own: [], competitor: [], market: [] }
    return {
      own: hotels.filter(h => h.tier === 'own'),
      competitor: hotels.filter(h => h.tier === 'competitor'),
      market: hotels.filter(h => h.tier === 'market'),
    }
  }, [hotels])

  const HotelCard: React.FC<{ hotel: Hotel }> = ({ hotel }) => (
    <div style={mergeStyles(styles.hotelCard, { borderLeft: `4px solid ${tierColor(hotel.tier)}` })}>
      <div style={styles.hotelHeader}>
        <div style={styles.hotelInfo}>
          <span style={styles.hotelName}>{hotel.name}</span>
          <div style={styles.hotelMeta}>
            {hotel.star_rating && <span>{hotel.star_rating} stars</span>}
            {hotel.review_score && <span>Score: {hotel.review_score}</span>}
            {hotel.review_count && <span>({hotel.review_count} reviews)</span>}
          </div>
        </div>
        <div style={styles.hotelActions}>
          {hotel.tier === 'competitor' && directHotels && directHotels.length > 0 && (
            <select
              value={hotel.direct_hotel_id ?? ''}
              onChange={e => linkMutation.mutate({
                hotelId: hotel.id,
                directId: e.target.value ? parseInt(e.target.value) : null,
              })}
              title="Link to a direct booking-engine competitor to show their direct rates in the matrix"
              style={mergeStyles(styles.tierSelect, { maxWidth: 190 })}
            >
              <option value="">No direct link</option>
              {directHotels.map(d => (
                <option key={d.id} value={d.id}>Direct: {d.name}</option>
              ))}
            </select>
          )}
          <select
            value={hotel.tier}
            onChange={e => tierMutation.mutate({ hotelId: hotel.id, tier: e.target.value })}
            style={mergeStyles(styles.tierSelect, { borderColor: tierColor(hotel.tier) })}
          >
            <option value="own">Own Hotel</option>
            <option value="competitor">Competitor</option>
            <option value="market">Market</option>
          </select>
        </div>
      </div>
      <div style={styles.hotelFooter}>
        <span style={{ fontSize: '11px', color: 'var(--text-mid)' }}>
          ID: {hotel.booking_com_id}
        </span>
        {hotel.last_seen_at && (
          <span style={{ fontSize: '11px', color: 'var(--text-mid)' }}>
            Last seen: {formatDateTime(hotel.last_seen_at)}
          </span>
        )}
      </div>
    </div>
  )

  if (isLoading) {
    return (
      <div style={styles.loading}>
        <div style={styles.spinner} />
        <span>Loading hotels...</span>
      </div>
    )
  }

  return (
    <div>
      {/* Filter */}
      <div style={styles.filterRow}>
        <span style={styles.filterLabel}>Filter:</span>
        {['', 'own', 'competitor', 'market'].map(t => (
          <button
            key={t}
            onClick={() => setTierFilter(t)}
            style={mergeStyles(
              buttonStyle(tierFilter === t ? 'secondary' : 'outline', 'small'),
              tierFilter === t ? {} : { opacity: 0.7 }
            )}
          >
            {t || 'All'} {t && hotels ? `(${grouped[t as keyof typeof grouped]?.length || 0})` : hotels ? `(${hotels.length})` : ''}
          </button>
        ))}
      </div>

      {/* Hotels */}
      {!hotels || hotels.length === 0 ? (
        <div style={styles.emptyState}>
          <h3 style={{ margin: 0, color: 'var(--text-dark)' }}>No Hotels Discovered</h3>
          <p style={{ color: 'var(--text-mid)', margin: '8px 0 0' }}>
            Run a scrape to discover hotels in your configured location.
          </p>
        </div>
      ) : (
        <div>
          {/* Own Hotel */}
          {grouped.own.length > 0 && (
            <div style={styles.tierSection}>
              <h3 style={mergeStyles(styles.tierHeader, { color: '#2563eb' })}>
                Your Hotel ({grouped.own.length})
              </h3>
              {grouped.own.map(h => <HotelCard key={h.id} hotel={h} />)}
            </div>
          )}

          {/* Competitors */}
          {grouped.competitor.length > 0 && (
            <div style={styles.tierSection}>
              <h3 style={mergeStyles(styles.tierHeader, { color: '#d97706' })}>
                Competitors ({grouped.competitor.length})
              </h3>
              {grouped.competitor.map(h => <HotelCard key={h.id} hotel={h} />)}
            </div>
          )}

          {/* Market */}
          {grouped.market.length > 0 && (
            <div style={styles.tierSection}>
              <h3 style={mergeStyles(styles.tierHeader, { color: 'var(--text-mid)' })}>
                Market ({grouped.market.length})
              </h3>
              {grouped.market.map(h => <HotelCard key={h.id} hotel={h} />)}
            </div>
          )}
        </div>
      )}
    </div>
  )
}

// ============================================
// RATE MATRIX TAB
// ============================================

const MonthSelector: React.FC<{
  value: string
  onChange: (value: string) => void
}> = ({ value, onChange }) => {
  const handlePrevMonth = () => {
    const [year, month] = value.split('-').map(Number)
    const date = new Date(year, month - 2, 1)
    onChange(`${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`)
  }

  const handleNextMonth = () => {
    const [year, month] = value.split('-').map(Number)
    const date = new Date(year, month, 1)
    onChange(`${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`)
  }

  const monthOptions = useMemo(() => {
    const options: { value: string; label: string }[] = []
    const now = new Date()
    for (let i = 0; i < 13; i++) {
      const date = new Date(now.getFullYear(), now.getMonth() + i, 1)
      const monthValue = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`
      const label = date.toLocaleDateString('en-GB', { month: 'long', year: 'numeric' })
      options.push({ value: monthValue, label })
    }
    return options
  }, [])

  return (
    <div style={styles.monthSelector}>
      <button onClick={handlePrevMonth} style={buttonStyle('outline', 'small')}>
        &larr;
      </button>
      <select
        value={value}
        onChange={(e) => onChange(e.target.value)}
        style={styles.monthDropdown}
      >
        {monthOptions.map((opt) => (
          <option key={opt.value} value={opt.value}>
            {opt.label}
          </option>
        ))}
      </select>
      <button onClick={handleNextMonth} style={buttonStyle('outline', 'small')}>
        &rarr;
      </button>
    </div>
  )
}

// ============================================
// RATE HISTORY MODAL
// ============================================

interface RateHistoryPoint { t: string | null; rate: number }
interface RateHistorySeries { room_type: string; points: RateHistoryPoint[] }
interface RateHistoryModal { hotelId: number; hotelName: string; stayDate: string }
interface RatePlan { meal: string; cancel: string; price: number; max_persons: number | null }
interface RoomSnapshot { room_type: string; rooms_left: number | null; availability_status: string; plans: RatePlan[] }
interface RateSnapshot { stay_date: string; legacy: boolean; rooms: RoomSnapshot[] }

const CHART_COLORS = ['#1a1a2e', '#c9a84c', '#16a34a', '#dc2626', '#7c3aed', '#0891b2', '#ea580c', '#be185d']

const RateHistoryModalComponent: React.FC<{ modal: RateHistoryModal; onClose: () => void }> = ({ modal, onClose }) => {
  const { data: histData, isLoading: histLoading } = useQuery<{ stay_date: string; series: RateHistorySeries[] }>({
    queryKey: ['hotel-rate-history', modal.hotelId, modal.stayDate],
    queryFn: async () => (await api.get(`/competitors/hotels/${modal.hotelId}/rate-history/${modal.stayDate}`)).data,
  })
  const { data: snap, isLoading: snapLoading } = useQuery<RateSnapshot>({
    queryKey: ['hotel-rate-snapshot', modal.hotelId, modal.stayDate],
    queryFn: async () => (await api.get(`/competitors/hotels/${modal.hotelId}/rate-snapshot/${modal.stayDate}`)).data,
  })

  const traces = useMemo(() => {
    if (!histData?.series) return []
    return histData.series.map((s, i) => ({
      type: 'scatter' as const,
      mode: 'lines+markers' as const,
      name: s.room_type,
      x: s.points.map(p => p.t),
      y: s.points.map(p => p.rate),
      line: { color: CHART_COLORS[i % CHART_COLORS.length], width: 2 },
      marker: { size: 5 },
      hovertemplate: `£%{y:.0f}<br>%{x}<extra>${s.room_type}</extra>`,
    }))
  }, [histData])

  const isLoading = histLoading || snapLoading

  return (
    <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center' }}
      onClick={onClose}>
      <div style={{ background: 'var(--card-bg)', borderRadius: 12, padding: 24, width: '90%', maxWidth: 820, maxHeight: '90vh', overflow: 'auto', boxShadow: '0 20px 60px rgba(0,0,0,0.3)' }}
        onClick={e => e.stopPropagation()}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 20 }}>
          <div>
            <div style={{ fontWeight: 700, fontSize: 16, color: 'var(--text-dark)' }}>{modal.hotelName}</div>
            <div style={{ fontSize: 13, color: 'var(--text-mid)', marginTop: 2 }}>{modal.stayDate}</div>
          </div>
          <button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-mid)', padding: 4 }}>
            <X size={18} strokeWidth={1.75} />
          </button>
        </div>

        {isLoading ? (
          <div style={{ textAlign: 'center', padding: 40, color: 'var(--text-mid)' }}>Loading…</div>
        ) : (
          <>
            {/* Current availability snapshot */}
            {snap && snap.rooms.length > 0 && (
              <div style={{ marginBottom: 24 }}>
                <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-mid)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 10 }}>
                  Current availability
                </div>
                {snap.rooms.map(room => (
                  <div key={room.room_type} style={{ marginBottom: 12, border: '1px solid var(--border)', borderRadius: 8, overflow: 'hidden' }}>
                    <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', background: 'var(--body-bg)', padding: '8px 12px' }}>
                      <span style={{ fontWeight: 600, fontSize: 13, color: 'var(--text-dark)' }}>{room.room_type}</span>
                      {room.availability_status === 'sold_out' ? (
                        <span style={{ fontSize: 11, color: '#dc2626', fontWeight: 500 }}>Sold out</span>
                      ) : room.rooms_left != null ? (
                        <span style={{ fontSize: 11, color: room.rooms_left <= 2 ? '#dc2626' : '#ea580c', fontWeight: 500 }}>
                          {room.rooms_left} left
                        </span>
                      ) : null}
                    </div>
                    {room.plans.length > 0 && (
                      <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12 }}>
                        <tbody>
                          {room.plans.map((plan, pi) => (
                            <tr key={pi} style={{ borderTop: '1px solid var(--border)' }}>
                              <td style={{ padding: '6px 12px', color: 'var(--text-mid)' }}>{plan.meal}</td>
                              <td style={{ padding: '6px 12px', color: plan.cancel.startsWith('Free') ? '#16a34a' : 'var(--text-mid)' }}>
                                {plan.cancel}
                              </td>
                              <td style={{ padding: '6px 12px', textAlign: 'right', fontWeight: 600, color: 'var(--text-dark)' }}>
                                £{plan.price.toLocaleString()}
                              </td>
                            </tr>
                          ))}
                        </tbody>
                      </table>
                    )}
                    {room.plans.length === 0 && room.availability_status !== 'sold_out' && (
                      <div style={{ padding: '6px 12px', fontSize: 12, color: 'var(--text-mid)' }}>No rates available</div>
                    )}
                  </div>
                ))}
              </div>
            )}
            {snap && snap.rooms.length === 0 && (
              <div style={{ fontSize: 13, color: 'var(--text-mid)', marginBottom: 20, padding: '12px', background: 'var(--body-bg)', borderRadius: 8 }}>
                No rate data from latest scrape for this date.
              </div>
            )}

            {/* Rate history chart */}
            {histData?.series && histData.series.length > 0 && (
              <>
                <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-mid)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 10 }}>
                  Rate history — best available per room type
                </div>
                <Plot
                  data={traces}
                  layout={{
                    margin: { t: 10, r: 10, b: 50, l: 55 },
                    height: 300,
                    legend: { orientation: 'h', y: -0.25 },
                    xaxis: { title: { text: '' }, tickformat: '%d %b %H:%M', type: 'date' },
                    yaxis: { title: { text: 'Rate (£)' }, tickprefix: '£', tickformat: ',.0f' },
                    plot_bgcolor: 'var(--body-bg)',
                    paper_bgcolor: 'var(--card-bg)',
                    font: { family: 'inherit', size: 11, color: 'var(--text-dark)' },
                    hovermode: 'x unified',
                  }}
                  config={{ displayModeBar: false, responsive: true }}
                  style={{ width: '100%' }}
                />
              </>
            )}
            {histData?.series && histData.series.length === 0 && (
              <div style={{ fontSize: 13, color: 'var(--text-mid)', padding: '12px', background: 'var(--body-bg)', borderRadius: 8 }}>
                No historical rate data yet — history builds up over time as daily scrapes run.
              </div>
            )}
          </>
        )}
      </div>
    </div>
  )
}

const RateMatrixTab: React.FC = () => {
  const [selectedMonth, setSelectedMonth] = useState(() => {
    const today = new Date()
    return `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}`
  })
  const [includeMarket, setIncludeMarket] = useState(false)
  const [hoveredCell, setHoveredCell] = useState<{ row: number; col: number } | null>(null)
  const [rangeMode, setRangeMode] = useState(false)
  const [customFrom, setCustomFrom] = useState(fmtDate(new Date()))
  const [customTo, setCustomTo] = useState(fmtDate(new Date(Date.now() + 13 * 86400000)))
  const [showDirect, setShowDirect] = useState(false)
  const [historyModal, setHistoryModal] = useState<RateHistoryModal | null>(null)
  const queryClient = useQueryClient()
  const todayStr = fmtDate(new Date())

  // The scrape endpoint returns immediately (runs in background), and the
  // scraper handles one job at a time, so queued jobs (single dates or
  // ranges) are dispatched sequentially: watch scraper status until a new
  // batch finishes, refetch the matrix, then start the next job.
  const [scrapeQueue, setScrapeQueue] = useState<ScrapeJob[]>([])
  const [scrapeWatch, setScrapeWatch] = useState<{ prevBatch: string | null, startedAt: number, timeoutMs: number } | null>(null)
  const [dispatchHoldUntil, setDispatchHoldUntil] = useState(0)
  const [retryTick, setRetryTick] = useState(0)

  const enqueueScrape = (from: string, to: string) => {
    setScrapeQueue(q => q.some(j => j.from === from && j.to === to) ? q : [...q, { from, to }])
  }

  const activeJob = scrapeWatch ? scrapeQueue[0] : undefined
  const scrapingDate = activeJob && activeJob.from === activeJob.to ? activeJob.from : null

  const dateScrapeM = useMutation({
    mutationFn: async (job: ScrapeJob) => {
      const st = (await api.get('/competitors/status')).data as ScraperStatus
      const days = Math.round((new Date(job.to).getTime() - new Date(job.from).getTime()) / 86400000) + 1
      setScrapeWatch({
        prevBatch: st?.last_scrape?.batch_id ?? null,
        startedAt: Date.now(),
        // generous per-date allowance: ranges take ~1 min per date
        timeoutMs: 10 * 60 * 1000 + days * 90 * 1000,
      })
      return (await api.post('/competitors/scrape', { from_date: job.from, to_date: job.to })).data
    },
    onError: (err: any) => {
      setScrapeWatch(null)
      if (err?.response?.status === 409) {
        // Server is busy with another scrape — keep the job queued, retry in 30s
        setDispatchHoldUntil(Date.now() + 30000)
        setTimeout(() => setRetryTick(t => t + 1), 31000)
      } else {
        setScrapeQueue(q => q.slice(1))
      }
    },
  })

  // Dispatch the next queued job when idle
  useEffect(() => {
    if (Date.now() < dispatchHoldUntil) return
    if (scrapeQueue.length && !scrapeWatch && !dateScrapeM.isPending) {
      dateScrapeM.mutate(scrapeQueue[0])
    }
  }, [scrapeQueue, scrapeWatch, dateScrapeM.isPending, dispatchHoldUntil, retryTick]) // eslint-disable-line react-hooks/exhaustive-deps

  // Also serves the date-header Booking.com links via location_name
  const { data: watchStatus } = useQuery<ScraperStatus>({
    queryKey: ['scraper-status'],
    queryFn: async () => (await api.get('/competitors/status')).data,
    refetchInterval: scrapeWatch !== null ? 5000 : false,
    staleTime: 60 * 1000,
  })
  const locationName = watchStatus?.location_name

  useEffect(() => {
    if (!scrapeWatch) return
    const ls = watchStatus?.last_scrape
    const finished = ls && ls.batch_id !== scrapeWatch.prevBatch && ls.status !== 'running'
    const timedOut = Date.now() - scrapeWatch.startedAt > scrapeWatch.timeoutMs
    if (finished || timedOut) {
      queryClient.invalidateQueries({ queryKey: ['competitor-matrix'] })
      queryClient.invalidateQueries({ queryKey: ['scraper-status'] })
      setScrapeWatch(null)
      setScrapeQueue(q => q.slice(1))
    }
  }, [watchStatus, scrapeWatch, queryClient])

  const onCellEnter = useCallback((row: number, col: number) => {
    setHoveredCell({ row, col })
  }, [])
  const onCellLeave = useCallback(() => setHoveredCell(null), [])

  const { fromDate, toDate } = useMemo(() => {
    if (rangeMode) return { fromDate: customFrom, toDate: customTo }
    const [year, month] = selectedMonth.split('-').map(Number)
    return {
      fromDate: fmtDate(new Date(year, month - 1, 1)),
      toDate: fmtDate(new Date(year, month, 0)),
    }
  }, [selectedMonth, rangeMode, customFrom, customTo])

  const { data, isLoading, error } = useQuery<RateMatrixResponse>({
    queryKey: ['competitor-matrix', fromDate, toDate, includeMarket],
    queryFn: async () => {
      const params = new URLSearchParams({
        from_date: fromDate,
        to_date: toDate,
        include_market: includeMarket.toString(),
      })
      return (await api.get(`/competitors/matrix?${params}`)).data
    },
    // While a scrape runs, refresh so columns fill in as dates complete
    refetchInterval: scrapeWatch !== null ? 30000 : false,
  })

  const dates = data?.dates || []
  const rates = data?.rates || {}

  // Sort hotels: own first, then competitor, then market
  const hotels = useMemo(() => {
    const tierPriority: Record<string, number> = { own: 0, competitor: 1, market: 2 }
    return [...(data?.hotels || [])].sort((a, b) => {
      const ta = tierPriority[a.tier] ?? 9
      const tb = tierPriority[b.tier] ?? 9
      if (ta !== tb) return ta - tb
      return (a.display_order ?? 999) - (b.display_order ?? 999)
    })
  }, [data?.hotels])

  // Latest scrape touching each date column, across ALL hotels (the backend
  // map covers hotels outside the matrix too — a partial scrape can refresh
  // a date without touching the displayed hotels). Fallback: compute from
  // the visible cells.
  const scrapedAtByDate = useMemo(() => {
    const result: Record<string, string | null> = {}
    for (const d of dates) {
      let latest: string | null = data?.last_scraped?.[d] ?? null
      for (const hotel of hotels) {
        const rate = (rates[hotel.id] || {})[d]
        if (rate?.scraped_at) {
          if (!latest || rate.scraped_at > latest) {
            latest = rate.scraped_at
          }
        }
      }
      result[d] = latest
    }
    return result
  }, [dates, hotels, rates, data?.last_scraped])

  // Own hotel rate by date (first 'own' tier hotel)
  const ownRateByDate = useMemo(() => {
    const ownHotel = hotels.find(h => h.tier === 'own')
    if (!ownHotel) return {} as Record<string, number | null>
    const result: Record<string, number | null> = {}
    for (const d of dates) {
      const r = (rates[ownHotel.id] || {})[d]
      result[d] = r?.rate_gross ?? null
    }
    return result
  }, [hotels, rates, dates])

  // Direct rates per competitor hotel per date (cheapest)
  const { data: directRatesMap } = useQuery<Record<number, Record<string, number | null>>>({
    queryKey: ['market-direct-rates', fromDate, toDate],
    queryFn: async () => {
      const compHotels = hotels.filter(h => h.tier === 'competitor' && h.direct_hotel_id)
      if (!compHotels.length) return {}
      const result: Record<number, Record<string, number | null>> = {}
      await Promise.all(compHotels.map(async h => {
        const directId = h.direct_hotel_id!
        try {
          const res = await api.get(`/direct/hotels/${directId}/dates`, {
            params: { from_date: fromDate, to_date: toDate }
          })
          result[h.id] = Object.fromEntries(
            (res.data.dates as any[]).map((row: any) => [row.stay_date, row.cheapest_rate])
          )
        } catch { result[h.id] = {} }
      }))
      return result
    },
    enabled: showDirect && hotels.length > 0,
  })

  // Our own direct rate comes from Newbook (best available), not the direct scraper
  const { data: ownDirectRates } = useQuery<Record<string, { rate: number; tariff: string }>>({
    queryKey: ['own-direct-rates', fromDate, toDate],
    queryFn: async () => {
      const res = await api.get('/competitors/own-direct-rates', {
        params: { from_date: fromDate, to_date: toDate }
      })
      return res.data.rates
    },
    enabled: showDirect,
  })

  if (isLoading) {
    return (
      <div style={styles.loading}>
        <div style={styles.spinner} />
        <span>Loading rate matrix...</span>
      </div>
    )
  }

  if (error) {
    return (
      <div style={styles.errorBox}>
        {(error as any)?.response?.data?.detail || 'Failed to load rate matrix'}
      </div>
    )
  }

  return (
    <div>
      {/* Controls */}
      <div style={{ ...styles.matrixControls, flexWrap: 'wrap', gap: 12 }}>
        {!rangeMode ? (
          <MonthSelector value={selectedMonth} onChange={setSelectedMonth} />
        ) : (
          <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            <input type="date" style={{ ...inputStyle, width: 136 }} value={customFrom}
              onChange={e => setCustomFrom(e.target.value)} />
            <span style={{ color: 'var(--text-mid)', fontSize: 12 }}>to</span>
            <input type="date" style={{ ...inputStyle, width: 136 }} value={customTo}
              onChange={e => setCustomTo(e.target.value)} />
          </div>
        )}
        <div style={{ display: 'flex', gap: 6 }}>
          {[{ label: '7d', days: 7 }, { label: '14d', days: 14 }, { label: '30d', days: 30 }, { label: '90d', days: 90 }].map(p => (
            <button key={p.label}
              style={buttonStyle('outline', 'small')}
              onClick={() => {
                setRangeMode(true)
                setCustomFrom(fmtDate(new Date()))
                setCustomTo(fmtDate(new Date(Date.now() + p.days * 86400000)))
              }}
            >
              {p.label}
            </button>
          ))}
          {rangeMode && (
            <button style={buttonStyle('secondary', 'small')} onClick={() => setRangeMode(false)}>
              Month view
            </button>
          )}
        </div>
        <div style={{ display: 'flex', gap: 6, alignItems: 'center', marginLeft: 'auto' }}>
          {[{ label: 'Scrape 7d', days: 7 }, { label: 'Scrape 30d', days: 30 }].map(p => {
            const from = fmtDate(new Date())
            const to = fmtDate(new Date(Date.now() + (p.days - 1) * 86400000))
            const queued = scrapeQueue.some(j => j.from === from && j.to === to)
            const active = activeJob && activeJob.from === from && activeJob.to === to
            return (
              <button key={p.label}
                style={{ ...buttonStyle('primary', 'small'), opacity: queued ? 0.7 : 1, display: 'inline-flex', alignItems: 'center', gap: 5 }}
                disabled={queued}
                onClick={() => enqueueScrape(from, to)}
                title={`Scrape today + next ${p.days - 1} days (~${p.days} min)`}
              >
                <RefreshCw size={13} strokeWidth={1.75} style={active ? { animation: 'spin 1.5s linear infinite' } : undefined} />
                {active ? 'Scraping…' : queued ? 'Queued' : p.label}
              </button>
            )
          })}
        </div>
        <label style={styles.checkboxLabel}>
          <input
            type="checkbox"
            checked={includeMarket}
            onChange={e => setIncludeMarket(e.target.checked)}
          />
          Include market hotels
        </label>
        <label style={styles.checkboxLabel}>
          <input
            type="checkbox"
            checked={showDirect}
            onChange={e => setShowDirect(e.target.checked)}
          />
          Show direct rates
        </label>
      </div>

      {hotels.length === 0 ? (
        <div style={styles.emptyState}>
          <h3 style={{ margin: 0, color: 'var(--text-dark)' }}>No Rate Data</h3>
          <p style={{ color: 'var(--text-mid)', margin: '8px 0 0' }}>
            Run a scrape and categorize hotels as competitors to see rate comparisons.
          </p>
        </div>
      ) : (
        <div style={styles.matrixContainer}>
          <table style={styles.matrixTable}>
            <thead>
              <tr>
                <th style={mergeStyles(styles.matrixTh, styles.stickyCol)}>Hotel</th>
                {dates.map((d, colIdx) => {
                  const scrapeAge = formatScrapeAge(scrapedAtByDate[d])
                  const isColHovered = hoveredCell?.col === colIdx
                  const queuePos = scrapeQueue.findIndex(j => j.from === d && j.to === d)
                  return (
                    <th
                      key={d}
                      style={mergeStyles(
                        styles.matrixTh,
                        styles.dateHeader,
                        isWeekend(d) ? styles.weekendHeader : {},
                        isColHovered ? styles.crosshairCol : {}
                      )}
                      title={scrapedAtByDate[d] ? `Scraped: ${new Date(scrapedAtByDate[d]!).toLocaleString('en-GB')}` : 'No data scraped'}
                    >
                      <div style={styles.dateHeaderContent}>
                        <span style={styles.dayOfWeek}>{formatDayOfWeek(d)}</span>
                        <span style={styles.dayNum}>{formatDateShort(d)}</span>
                        {scrapeAge ? (
                          <span style={styles.scrapeAge}>{scrapeAge}</span>
                        ) : null}
                        <div style={{ display: 'flex', gap: 2, alignItems: 'center', justifyContent: 'center' }}>
                          <button
                            onClick={() => enqueueScrape(d, d)}
                            disabled={queuePos >= 0}
                            style={mergeStyles(
                              styles.scrapeBtn,
                              queuePos >= 0 ? styles.scrapeBtnActive : {}
                            )}
                            title={
                              scrapingDate === d ? `Scraping ${d}…`
                                : queuePos >= 0 ? `Queued (#${queuePos + 1})`
                                : `Scrape ${d}`
                            }
                          >
                            {scrapingDate === d ? '...' : queuePos >= 0 ? `#${queuePos + 1}` : '↻'}
                          </button>
                          {locationName && d >= todayStr && (
                            <a
                              href={buildSearchUrl(locationName, d)}
                              target="_blank"
                              rel="noopener noreferrer"
                              title={`View ${d} search on Booking.com`}
                              style={{ color: 'var(--text-mid)', display: 'inline-flex', alignItems: 'center' }}
                            >
                              <Eye size={11} strokeWidth={1.75} />
                            </a>
                          )}
                        </div>
                      </div>
                    </th>
                  )
                })}
              </tr>
            </thead>
            <tbody>
              {hotels.map((hotel, rowIdx) => {
                const hotelRates = rates[hotel.id] || {}
                const isRowHovered = hoveredCell?.row === rowIdx
                return (
                  <React.Fragment key={hotel.id}>
                  <tr>
                    <td style={mergeStyles(
                      styles.matrixTd, styles.stickyCol, styles.hotelNameCell,
                      isRowHovered ? styles.crosshairRow : {}
                    )}>
                      <div style={styles.matrixHotelInfo}>
                        <span
                          style={mergeStyles(styles.tierDot, { background: tierColor(hotel.tier) })}
                        />
                        <span style={styles.matrixHotelName}>{hotel.name}</span>
                        {hotel.star_rating && (
                          <span style={styles.matrixStars}>{hotel.star_rating}*</span>
                        )}
                      </div>
                    </td>
                    {dates.map((d, colIdx) => {
                      const rate = hotelRates[d]
                      const isAvailable = rate?.availability_status === 'available'
                      const isSoldOut = rate?.availability_status === 'sold_out'
                      const isPast = d < todayStr
                      const isSoldOrPast = isSoldOut || isPast

                      let cellStyle: React.CSSProperties = styles.matrixCellEmpty
                      if (rate) {
                        if (isPast) {
                          cellStyle = styles.matrixCellPast
                        } else if (isAvailable && rate.rate_gross) {
                          cellStyle = styles.matrixCellAvailable
                        } else if (isSoldOut) {
                          cellStyle = styles.matrixCellSoldOut
                        } else {
                          cellStyle = styles.matrixCellNoRate
                        }
                      }

                      // Stale = this cell wasn't touched by the column's most
                      // recent scrape (e.g. the hotel was on a page that failed)
                      const colLatest = scrapedAtByDate[d]
                      const isStale = !isPast && !!(rate?.scraped_at && colLatest &&
                        new Date(colLatest).getTime() - new Date(rate.scraped_at).getTime() > 60 * 60 * 1000)
                      const staleMark = isStale ? '*' : ''

                      const lastAvailRate = rate?.last_available_rate
                        ? formatCurrency(rate.last_available_rate)
                        : null

                      // For sold-out / past: show last known available rate with strikethrough
                      let rateText = ''
                      let strikethrough = false
                      if (!rate) {
                        rateText = ''
                      } else if (isSoldOrPast && lastAvailRate) {
                        rateText = lastAvailRate + staleMark
                        strikethrough = true
                      } else if (isSoldOrPast) {
                        rateText = isSoldOut ? 'Sold' : '—'
                      } else if (isAvailable && rate.rate_gross) {
                        rateText = formatCurrency(rate.rate_gross) + staleMark
                      } else {
                        rateText = '—'
                      }

                      const tooltip = rate ? [
                        rate.room_type,
                        rate.breakfast_included ? 'Breakfast incl.' : null,
                        rate.free_cancellation ? 'Free cancel' : null,
                        rate.rooms_left ? `${rate.rooms_left} left` : null,
                        lastAvailRate && isSoldOrPast ? `Last available: ${lastAvailRate}` : null,
                        rate.scraped_at ? `Scraped: ${new Date(rate.scraped_at).toLocaleString('en-GB', { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' })}` : null,
                        isStale ? 'Not updated in latest scrape' : null,
                      ].filter(Boolean).join(' | ') : ''

                      // Build booking.com link
                      let bookingUrl: string | null = null
                      if (!isPast && hotel.booking_com_url) {
                        const checkin = d
                        const co = new Date(d + 'T00:00:00')
                        co.setDate(co.getDate() + 1)
                        const checkout = fmtDate(co)
                        try {
                          const url = new URL(hotel.booking_com_url)
                          const stripParams = ['checkin', 'checkout', 'group_adults', 'group_children', 'req_adults', 'req_children', 'no_rooms']
                          stripParams.forEach(p => url.searchParams.delete(p))
                          url.searchParams.set('checkin', checkin)
                          url.searchParams.set('checkout', checkout)
                          url.searchParams.set('group_adults', '2')
                          bookingUrl = url.toString()
                        } catch {
                          bookingUrl = hotel.booking_com_url
                        }
                      }

                      // Price index badge for competitor rows (use last_available_rate for sold/past)
                      const rateForIndex = isSoldOrPast ? rate?.last_available_rate : rate?.rate_gross
                      let priceIndexBadge: React.ReactNode = null
                      if (hotel.tier === 'competitor' && rateForIndex && ownRateByDate[d]) {
                        const delta = Math.round((rateForIndex / ownRateByDate[d]! - 1) * 100)
                        const bg = delta > 5 ? '#dcfce7' : delta < -15 ? '#fee2e2' : delta < -5 ? '#fef3c7' : '#f1f5f9'
                        const fg = delta > 5 ? '#16a34a' : delta < -15 ? '#dc2626' : delta < -5 ? '#d97706' : '#64748b'
                        priceIndexBadge = (
                          <span style={{ fontSize: 9, fontWeight: 700, color: fg, background: bg,
                            borderRadius: 4, padding: '0 3px', lineHeight: '14px' }}>
                            {delta > 0 ? '+' : ''}{delta}%
                          </span>
                        )
                      }

                      const showEye = !isPast && !!bookingUrl && !!rate
                      const showHistory = !!rate

                      const isRowH = hoveredCell?.row === rowIdx
                      const isColH = hoveredCell?.col === colIdx
                      const isCellH = isRowH && isColH

                      return (
                        <td
                          key={d}
                          style={mergeStyles(
                            styles.matrixTd,
                            cellStyle,
                            isStale ? { fontStyle: 'italic' } : {},
                            isWeekend(d) ? styles.weekendCell : {},
                            isRowH || isColH ? styles.crosshairHighlight : {},
                            isCellH ? styles.crosshairCell : {}
                          )}
                          title={tooltip}
                          onMouseEnter={() => onCellEnter(rowIdx, colIdx)}
                          onMouseLeave={onCellLeave}
                        >
                          <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 1 }}>
                            <span style={{ textDecoration: strikethrough ? 'line-through' : 'none' }}>
                              {rateText}
                            </span>
                            {priceIndexBadge}
                            {(showEye || showHistory) && (
                              <div style={{ display: 'flex', gap: 4, marginTop: 2, opacity: 0.6 }}>
                                {showEye && (
                                  <a
                                    href={bookingUrl!}
                                    target="_blank"
                                    rel="noopener noreferrer"
                                    title="View on Booking.com"
                                    style={{ color: 'inherit', display: 'inline-flex', alignItems: 'center', lineHeight: 1 }}
                                    onClick={e => e.stopPropagation()}
                                  >
                                    <Eye size={12} strokeWidth={1.75} />
                                  </a>
                                )}
                                {showHistory && (
                                  <button
                                    title="Rate history"
                                    onClick={() => setHistoryModal({ hotelId: hotel.id, hotelName: hotel.name, stayDate: d })}
                                    style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 0, color: 'inherit', display: 'inline-flex', alignItems: 'center', lineHeight: 1 }}
                                  >
                                    <LineChart size={12} strokeWidth={1.75} />
                                  </button>
                                )}
                              </div>
                            )}
                          </div>
                        </td>
                      )
                    })}
                  </tr>
                  {/* Own hotel direct sub-row — Newbook best available */}
                  {showDirect && hotel.tier === 'own' && ownDirectRates &&
                    Object.keys(ownDirectRates).length > 0 && (
                    <tr style={{ background: '#fafbfc' }}>
                      <td style={{ ...styles.matrixTd, ...styles.stickyCol, paddingLeft: 28, fontSize: 11, color: 'var(--text-mid)', fontStyle: 'italic' }}>
                        Direct (Newbook)
                      </td>
                      {dates.map(d => {
                        const own = ownDirectRates[d]
                        return (
                          <td key={d} title={own?.tariff}
                            style={{ ...styles.matrixTd, fontSize: 11, color: own ? 'var(--text-dark)' : 'var(--text-mid)',
                            background: isWeekend(d) ? '#fdf8f0' : undefined }}>
                            {own ? `£${Number(own.rate).toFixed(0)}` : '—'}
                          </td>
                        )
                      })}
                    </tr>
                  )}
                  {/* Direct rates sub-row — only when this hotel actually has direct rates */}
                  {showDirect && hotel.tier === 'competitor' &&
                    Object.values(directRatesMap?.[hotel.id] || {}).some(v => v != null) && (
                    <tr style={{ background: '#fafbfc' }}>
                      <td style={{ ...styles.matrixTd, ...styles.stickyCol, paddingLeft: 28, fontSize: 11, color: 'var(--text-mid)', fontStyle: 'italic' }}>
                        Direct
                      </td>
                      {dates.map(d => {
                        const directRate = directRatesMap?.[hotel.id]?.[d] ?? null
                        return (
                          <td key={d} style={{ ...styles.matrixTd, fontSize: 11, color: directRate ? 'var(--text-dark)' : 'var(--text-mid)',
                            background: isWeekend(d) ? '#fdf8f0' : undefined }}>
                            {directRate ? `£${Number(directRate).toFixed(0)}` : '—'}
                          </td>
                        )
                      })}
                    </tr>
                  )}
                  </React.Fragment>
                )
              })}
            </tbody>
          </table>
        </div>
      )}

      {historyModal && (
        <RateHistoryModalComponent modal={historyModal} onClose={() => setHistoryModal(null)} />
      )}

      {/* Legend */}
      <div style={styles.legend}>
        <span style={styles.legendTitle}>Legend:</span>
        <span style={mergeStyles(styles.legendItem, styles.matrixCellAvailable)}>Available</span>
        <span style={mergeStyles(styles.legendItem, styles.matrixCellSoldOut)}>Sold Out (strikethrough = last rate)</span>
        <span style={mergeStyles(styles.legendItem, styles.matrixCellNoRate)}>No Rate</span>
        <span style={mergeStyles(styles.legendItem, styles.matrixCellPast)}>Past</span>
        <span style={mergeStyles(styles.legendItem, styles.matrixCellEmpty)}>No Data</span>
        <span style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: '8px', fontSize: '11px' }}>
          <span style={mergeStyles(styles.tierDot, { background: '#2563eb' })} /> Own
          <span style={mergeStyles(styles.tierDot, { background: '#d97706' })} /> Competitor
          <span style={mergeStyles(styles.tierDot, { background: '#64748b' })} /> Market
        </span>
      </div>
    </div>
  )
}

// ============================================
// MAIN COMPONENT
// ============================================

const CompetitorRates: React.FC = () => {
  const [activeTab, setActiveTab] = useState<TabId>('matrix')

  const { data: status, isLoading: statusLoading } = useQuery<ScraperStatus>({
    queryKey: ['scraper-status'],
    queryFn: async () => (await api.get('/competitors/status')).data,
    refetchInterval: 30000,
  })

  const tabs: { id: TabId; label: string }[] = [
    { id: 'matrix', label: 'Rate Matrix' },
    { id: 'hotels', label: 'Hotels' },
    { id: 'parity', label: 'Parity Alerts' },
    { id: 'settings', label: 'Scraper Settings' },
  ]

  return (
    <div style={styles.container}>
      {/* Header */}
      <div style={styles.pageHeader}>
        <div>
          <h1 style={styles.title}>Competitor Rates</h1>
          <p style={styles.subtitle}>
            Compare rates across competitor hotels from Booking.com
          </p>
        </div>
      </div>

      {/* Status Bar */}
      <StatusPanel status={status} isLoading={statusLoading} />

      {/* Tabs */}
      <div style={styles.tabBar}>
        {tabs.map(tab => (
          <button
            key={tab.id}
            onClick={() => setActiveTab(tab.id)}
            style={mergeStyles(
              styles.tab,
              activeTab === tab.id ? styles.tabActive : {}
            )}
          >
            {tab.label}
          </button>
        ))}
      </div>

      {/* Tab Content */}
      <div style={styles.tabContent}>
        {activeTab === 'matrix' && <RateMatrixTab />}
        {activeTab === 'hotels' && <HotelsTab />}
        {activeTab === 'parity' && <ParityAlertsTab />}
        {activeTab === 'settings' && <SettingsTab />}
      </div>
    </div>
  )
}

// ============================================
// STYLES
// ============================================

const styles: Record<string, React.CSSProperties> = {
  container: {
    padding: '24px',
    maxWidth: '100%',
    margin: '0 auto',
  },
  pageHeader: {
    marginBottom: '16px',
  },
  title: {
    fontSize: '24px',
    fontWeight: 700,
    color: 'var(--text-dark)',
    margin: 0,
  },
  subtitle: {
    fontSize: '13px',
    color: 'var(--text-mid)',
    margin: '4px 0 0',
  },

  // Status bar
  statusBar: {
    display: 'flex',
    gap: '24px',
    padding: '16px',
    background: 'var(--card-bg)',
    borderRadius: '10px',
    boxShadow: 'var(--shadow-sm)',
    marginBottom: '16px',
    flexWrap: 'wrap',
    alignItems: 'center',
  },
  statusItem: {
    display: 'flex',
    alignItems: 'center',
    gap: '8px',
  },
  statusLabel: {
    fontSize: '11px',
    color: 'var(--text-mid)',
    textTransform: 'uppercase',
    fontWeight: 500,
  },

  // Tabs
  tabBar: {
    display: 'flex',
    gap: '4px',
    borderBottom: '2px solid var(--card-border)',
    marginBottom: '24px',
  },
  tab: {
    padding: '8px 24px',
    background: 'transparent',
    border: 'none',
    borderBottom: '2px solid transparent',
    cursor: 'pointer',
    fontSize: '13px',
    fontWeight: 500,
    color: 'var(--text-mid)',
    marginBottom: '-2px',
    transition: 'all 0.2s',
  },
  tabActive: {
    color: 'var(--navy)',
    borderBottomColor: 'var(--navy)',
    fontWeight: 600,
  },
  tabContent: {
    minHeight: '300px',
  },

  // Cards
  card: {
    background: 'var(--card-bg)',
    borderRadius: '10px',
    padding: '24px',
    boxShadow: 'var(--shadow-md)',
  },
  cardTitle: {
    fontSize: '16px',
    fontWeight: 600,
    color: 'var(--text-dark)',
    margin: '0 0 4px',
  },
  cardDescription: {
    fontSize: '13px',
    color: 'var(--text-mid)',
    margin: '0 0 16px',
  },

  // Settings
  settingsGrid: {
    display: 'grid',
    gridTemplateColumns: 'repeat(auto-fit, minmax(350px, 1fr))',
    gap: '24px',
  },
  formRow: {
    display: 'flex',
    gap: '16px',
    flexWrap: 'wrap',
  },
  formGroup: {
    flex: 1,
    minWidth: '200px',
  },
  formGroupSmall: {
    width: '80px',
  },
  controlRow: {
    display: 'flex',
    gap: '16px',
    flexWrap: 'wrap',
  },
  errorText: {
    color: 'var(--danger)',
    fontSize: '13px',
    marginTop: '8px',
  },
  hintText: {
    color: 'var(--text-mid)',
    fontSize: '11px',
    marginTop: '8px',
    fontStyle: 'italic',
  },

  // Hotels
  filterRow: {
    display: 'flex',
    alignItems: 'center',
    gap: '8px',
    marginBottom: '24px',
    flexWrap: 'wrap',
  },
  filterLabel: {
    fontSize: '13px',
    fontWeight: 500,
    color: 'var(--text-mid)',
  },
  tierSection: {
    marginBottom: '24px',
  },
  tierHeader: {
    fontSize: '14px',
    fontWeight: 600,
    margin: '0 0 8px',
  },
  hotelCard: {
    background: 'var(--card-bg)',
    borderRadius: '10px',
    padding: '16px',
    boxShadow: 'var(--shadow-sm)',
    marginBottom: '8px',
  },
  hotelHeader: {
    display: 'flex',
    justifyContent: 'space-between',
    alignItems: 'center',
    gap: '16px',
  },
  hotelInfo: {
    flex: 1,
  },
  hotelName: {
    fontSize: '13px',
    fontWeight: 600,
    color: 'var(--text-dark)',
  },
  hotelMeta: {
    display: 'flex',
    gap: '16px',
    fontSize: '11px',
    color: 'var(--text-mid)',
    marginTop: '4px',
  },
  hotelActions: {
    display: 'flex',
    gap: '8px',
  },
  tierSelect: {
    padding: '4px 8px',
    borderRadius: '6px',
    border: '1px solid var(--card-border)',
    fontSize: '11px',
    cursor: 'pointer',
    background: 'var(--card-bg)',
  },
  hotelFooter: {
    display: 'flex',
    justifyContent: 'space-between',
    marginTop: '8px',
    paddingTop: '8px',
    borderTop: '1px solid var(--card-border)',
  },
  emptyState: {
    textAlign: 'center',
    padding: '48px',
    background: 'var(--card-bg)',
    borderRadius: '10px',
    boxShadow: 'var(--shadow-sm)',
  },

  // Rate Matrix
  matrixControls: {
    display: 'flex',
    alignItems: 'center',
    gap: '24px',
    marginBottom: '24px',
    flexWrap: 'wrap',
  },
  monthSelector: {
    display: 'flex',
    alignItems: 'center',
    gap: '8px',
  },
  monthDropdown: {
    fontSize: '14px',
    fontWeight: 500,
    color: 'var(--text-dark)',
    padding: '4px 8px',
    borderRadius: '6px',
    border: '1px solid var(--card-border)',
    background: 'var(--card-bg)',
    cursor: 'pointer',
    minWidth: '160px',
  },
  checkboxLabel: {
    display: 'flex',
    alignItems: 'center',
    gap: '8px',
    fontSize: '13px',
    color: 'var(--text-mid)',
    cursor: 'pointer',
  },
  matrixContainer: {
    overflowX: 'auto',
    background: 'var(--card-bg)',
    borderRadius: '10px',
    boxShadow: 'var(--shadow-md)',
  },
  matrixTable: {
    width: '100%',
    borderCollapse: 'collapse',
    fontSize: '11px',
    minWidth: '800px',
  },
  matrixTh: {
    padding: '8px',
    borderBottom: '2px solid var(--card-border)',
    textAlign: 'center',
    fontWeight: 600,
    color: 'var(--text-dark)',
    whiteSpace: 'nowrap',
    background: 'var(--card-bg)',
    fontSize: '11px',
  },
  matrixTd: {
    padding: '4px 8px',
    borderBottom: '1px solid var(--card-border)',
    textAlign: 'center',
    whiteSpace: 'nowrap',
    fontSize: '11px',
  },
  stickyCol: {
    position: 'sticky',
    left: 0,
    background: 'var(--card-bg)',
    zIndex: 10,
    textAlign: 'left',
    minWidth: '180px',
    maxWidth: '220px',
    borderRight: '1px solid var(--card-border)',
  },
  hotelNameCell: {
    fontWeight: 500,
    overflow: 'hidden',
    textOverflow: 'ellipsis',
  },
  matrixHotelInfo: {
    display: 'flex',
    alignItems: 'center',
    gap: '8px',
  },
  tierDot: {
    width: '8px',
    height: '8px',
    borderRadius: '50%',
    flexShrink: 0,
    display: 'inline-block',
  },
  matrixHotelName: {
    overflow: 'hidden',
    textOverflow: 'ellipsis',
    whiteSpace: 'nowrap',
  },
  matrixStars: {
    color: '#d97706',
    fontSize: '11px',
    flexShrink: 0,
  },
  dateHeader: {
    minWidth: '50px',
    padding: '4px',
  },
  dateHeaderContent: {
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    gap: '2px',
  },
  dayOfWeek: {
    fontSize: '11px',
    color: 'var(--text-mid)',
  },
  dayNum: {
    fontSize: '13px',
    fontWeight: 600,
  },
  scrapeAge: {
    fontSize: '9px',
    color: 'var(--success)',
    fontWeight: 400,
    opacity: 0.8,
    lineHeight: 1,
  },
  weekendHeader: {
    background: 'var(--body-bg)',
  },
  weekendCell: {
    borderLeft: '2px solid var(--card-border)',
  },
  matrixCellAvailable: {
    background: '#dcfce7',
    color: 'var(--success)',
    fontWeight: 600,
  },
  matrixCellSoldOut: {
    background: '#fef3c7',
    color: '#d97706',
  },
  matrixCellNoRate: {
    background: '#fef9ec',
    color: '#b45309',
  },
  matrixCellEmpty: {
    background: 'var(--body-bg)',
    color: 'var(--text-mid)',
  },
  matrixCellPast: {
    background: '#f1f5f9',
    color: '#94a3b8',
  },
  crosshairHighlight: {
    boxShadow: 'inset 0 0 0 1px #1a1a2e33',
    background: '#1a1a2e08',
  },
  crosshairCell: {
    boxShadow: 'inset 0 0 0 2px var(--navy)',
  },
  crosshairRow: {
    boxShadow: 'inset 0 0 0 1px #1a1a2e33',
    background: '#1a1a2e08',
  },
  crosshairCol: {
    boxShadow: 'inset 0 0 0 1px #1a1a2e33',
    background: '#1a1a2e08',
  },
  scrapeBtn: {
    background: 'none',
    border: '1px solid var(--card-border)',
    borderRadius: '4px',
    cursor: 'pointer',
    fontSize: '10px',
    lineHeight: 1,
    padding: '2px 4px',
    color: 'var(--text-mid)',
    opacity: 0.6,
    transition: 'opacity 0.15s',
  },
  scrapeBtnActive: {
    opacity: 1,
    color: 'var(--navy)',
    borderColor: 'var(--navy)',
  },

  // Shared
  table: {
    width: '100%',
    borderCollapse: 'collapse',
    fontSize: '13px',
  },
  th: {
    padding: '8px',
    borderBottom: '2px solid var(--card-border)',
    textAlign: 'left',
    fontWeight: 600,
    color: 'var(--text-dark)',
    whiteSpace: 'nowrap',
    fontSize: '11px',
  },
  td: {
    padding: '8px',
    borderBottom: '1px solid var(--card-border)',
    fontSize: '13px',
  },
  historyTable: {
    overflowX: 'auto',
  },
  noData: {
    textAlign: 'center',
    padding: '24px',
    color: 'var(--text-mid)',
    fontSize: '13px',
  },
  legend: {
    display: 'flex',
    alignItems: 'center',
    gap: '16px',
    marginTop: '24px',
    padding: '16px',
    background: 'var(--card-bg)',
    borderRadius: '10px',
    fontSize: '13px',
    flexWrap: 'wrap',
  },
  legendTitle: {
    fontWeight: 600,
    color: 'var(--text-dark)',
  },
  legendItem: {
    padding: '4px 8px',
    borderRadius: '4px',
    fontSize: '11px',
  },
  loading: {
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    justifyContent: 'center',
    padding: '48px',
    gap: '16px',
    color: 'var(--text-mid)',
  },
  spinner: {
    width: '40px',
    height: '40px',
    border: '3px solid var(--card-border)',
    borderTop: '3px solid var(--navy)',
    borderRadius: '50%',
    animation: 'spin 1s linear infinite',
  },
  errorBox: {
    padding: '24px',
    background: '#fee2e2',
    color: 'var(--danger)',
    borderRadius: '10px',
  },

  // Schedule
  scheduleGrid: {
    display: 'flex',
    flexDirection: 'column',
    gap: '8px',
  },
  scheduleTier: {
    padding: '8px',
    background: 'var(--body-bg)',
    borderRadius: '6px',
  },
  scheduleTierHeader: {
    display: 'flex',
    justifyContent: 'space-between',
    alignItems: 'center',
  },
  scheduleTierName: {
    fontSize: '13px',
    fontWeight: 600,
    color: 'var(--text-dark)',
    textTransform: 'capitalize',
  },
  scheduleTierDesc: {
    fontSize: '11px',
    color: 'var(--text-mid)',
    margin: '4px 0 0',
  },
  scheduleTierRange: {
    fontSize: '11px',
    color: 'var(--text-mid)',
    margin: '2px 0 0',
    fontFamily: 'monospace',
  },
  queuePanel: {
    padding: '8px',
    background: 'var(--body-bg)',
    borderRadius: '6px',
  },
  queueStats: {
    display: 'flex',
    gap: '8px',
    flexWrap: 'wrap',
  },

  // Coverage grid
  coverageContainer: {
    display: 'flex',
    flexDirection: 'column',
    gap: '16px',
  },
  coverageLegend: {
    display: 'flex',
    alignItems: 'center',
    gap: '8px',
    fontSize: '11px',
    flexWrap: 'wrap',
  },
  coverageLegendItem: {
    padding: '2px 8px',
    borderRadius: '4px',
    fontSize: '11px',
  },
  coverageMonth: {
    display: 'flex',
    alignItems: 'flex-start',
    gap: '8px',
  },
  coverageMonthLabel: {
    fontSize: '11px',
    fontWeight: 600,
    color: 'var(--text-dark)',
    minWidth: '70px',
    paddingTop: '3px',
    flexShrink: 0,
  },
  coverageCells: {
    display: 'flex',
    flexWrap: 'wrap',
    gap: '3px',
  },
  coverageCell: {
    width: '32px',
    height: '28px',
    borderRadius: '3px',
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    justifyContent: 'center',
    cursor: 'default',
    lineHeight: 1,
    border: '1px solid rgba(0,0,0,0.06)',
  },
  coverageCellDay: {
    fontSize: '9px',
    fontWeight: 600,
  },
  coverageCellTier: {
    fontSize: '7px',
    opacity: 0.7,
  },
}

export default CompetitorRates
