/**
 * HomePage — data-visualization map view (M2-T09).
 *
 * Renders a heat map of location records (where you've been) and poo records
 * (where the dog poops), plus a toggleable scatter layer for point-select
 * edit/delete (reusing T10's modals + hooks).
 *
 * Data fetching and all state live here; the map itself is fully isolated in
 * src/map/RecordsMap.tsx (the ONLY place that imports leaflet).
 */

import { useState, useMemo } from 'react'
import { useQuery } from '@tanstack/react-query'
import {
  Stack,
  Group,
  Switch,
  TextInput,
  Button,
  Select,
  ActionIcon,
  Tooltip,
  Paper,
  Text,
  Box,
  Loader,
  Alert,
  Badge,
  useComputedColorScheme,
} from '@mantine/core'
import { ChevronLeft, ChevronRight } from 'react-feather'

import apiClient from '../api/client'
import {
  locationsToHeatPoints,
  pooToHeatPoints,
  locationsToMapPoints,
  pooToMapPoints,
  filterPooByTimeWindow,
  daysAgoISO,
  nowISO,
  TIME_PRESETS,
  presetRange,
  shiftRange,
} from '../map'
import { RecordsMap } from '../map'
import {
  EditLocationModal,
  EditPooModal,
  ConfirmDeleteModal,
  useDeleteLocation,
  useDeletePoo,
} from '../records'
import type { LocationRecord, PooRecord } from '../records'

// ---------------------------------------------------------------------------
// Data hooks (query-key prefix: ['locations', ...] / ['poo', ...])
// ---------------------------------------------------------------------------

function useLocations(start: string | null, end: string | null) {
  return useQuery({
    queryKey: ['locations', { start, end, limit: 5000 }],
    queryFn: async () => {
      const res = await apiClient.GET('/api/locations', {
        params: {
          query: {
            limit: 5000,
            offset: 0,
            ...(start ? { start } : {}),
            ...(end ? { end } : {}),
          },
        },
      })
      return res.data?.items ?? []
    },
  })
}

/**
 * Poo endpoint has no server-side time filter — fetch a large page (max 1000)
 * and client-filter by timestamp below.
 */
function usePoo() {
  return useQuery({
    queryKey: ['poo', { limit: 1000 }],
    queryFn: async () => {
      const res = await apiClient.GET('/api/poo', {
        params: { query: { limit: 1000, offset: 0 } },
      })
      return res.data?.items ?? []
    },
  })
}

// ---------------------------------------------------------------------------
// Point-select state (which record is selected + which modal to show)
// ---------------------------------------------------------------------------

type SelectionState =
  | { kind: 'none' }
  | { kind: 'editLocation'; record: LocationRecord }
  | { kind: 'deleteLocation'; record: LocationRecord }
  | { kind: 'editPoo'; record: PooRecord }
  | { kind: 'deletePoo'; record: PooRecord }

// ---------------------------------------------------------------------------
// HomePage
// ---------------------------------------------------------------------------

export function HomePage() {
  // ------ Time-window state -----------------------------------------------
  // Default: last 30 days → now
  const [startInput, setStartInput] = useState(() => {
    const d = new Date()
    d.setUTCDate(d.getUTCDate() - 30)
    return d.toISOString().slice(0, 16) // "YYYY-MM-DDTHH:MM"
  })
  const [endInput, setEndInput] = useState(() => nowISO().slice(0, 16))
  // Applied (committed) window — updated on Apply / preset / shift
  const [appliedStart, setAppliedStart] = useState<string | null>(() => daysAgoISO(30))
  const [appliedEnd, setAppliedEnd] = useState<string | null>(() => nowISO())
  // Which quick-range preset is currently active (null = custom / shifted range)
  const [activePreset, setActivePreset] = useState<string | null>(null)

  // Set both the committed window and the editable inputs from an ISO [start, end].
  function setWindow(startISO: string, endISO: string) {
    setAppliedStart(startISO)
    setAppliedEnd(endISO)
    setStartInput(startISO.slice(0, 16))
    setEndInput(endISO.slice(0, 16))
  }

  // Pick a quick range: fill from-to ending at now, apply immediately (Grafana-style).
  function applyPreset(value: string | null) {
    const preset = TIME_PRESETS.find((p) => p.value === value)
    if (!preset) return
    const { start, end } = presetRange(preset.spanMs)
    setWindow(start, end)
    setActivePreset(value)
  }

  // Shift the committed window by its own span. -1 = earlier, +1 = later.
  function shiftWindow(direction: -1 | 1) {
    if (!appliedStart || !appliedEnd) return
    const { start, end } = shiftRange(appliedStart, appliedEnd, direction)
    setWindow(start, end)
    // A shifted window is an absolute range, no longer "now - X".
    setActivePreset(null)
  }

  // ------ Layer toggle state -----------------------------------------------
  const [showLocationHeat, setShowLocationHeat] = useState(true)
  const [showPooHeat, setShowPooHeat] = useState(true)
  const [showScatter, setShowScatter] = useState(false)

  // ------ Data fetching ----------------------------------------------------
  const locationsQuery = useLocations(appliedStart, appliedEnd)
  const pooQuery = usePoo()

  // Client-side time-filter for poo (server has no filter)
  const filteredPoo = useMemo(
    () => filterPooByTimeWindow(pooQuery.data ?? [], appliedStart, appliedEnd),
    [pooQuery.data, appliedStart, appliedEnd],
  )

  // Derived map data
  const locationHeatPoints = useMemo(
    () => locationsToHeatPoints(locationsQuery.data ?? []),
    [locationsQuery.data],
  )
  const pooHeatPoints = useMemo(
    () => pooToHeatPoints(filteredPoo),
    [filteredPoo],
  )
  const locationScatterPoints = useMemo(
    () => locationsToMapPoints(locationsQuery.data ?? []),
    [locationsQuery.data],
  )
  const pooScatterPoints = useMemo(
    () => pooToMapPoints(filteredPoo),
    [filteredPoo],
  )

  // ------ Point-select state -----------------------------------------------
  const [selection, setSelection] = useState<SelectionState>({ kind: 'none' })

  const deleteLocationMut = useDeleteLocation()
  const deletePooMut = useDeletePoo()

  // Handlers
  function handleSelectLocation(record: LocationRecord) {
    setSelection({ kind: 'editLocation', record })
  }
  function handleSelectPoo(record: PooRecord) {
    setSelection({ kind: 'editPoo', record })
  }

  function applyWindow() {
    // Convert local datetime-local inputs (which have no TZ) to ISO8601
    // by appending :00Z if needed. Input is "YYYY-MM-DDTHH:MM".
    const toISO = (s: string) => (s ? s + ':00Z' : null)
    setAppliedStart(toISO(startInput))
    setAppliedEnd(toISO(endInput))
    // Manually-applied range is custom, not a preset.
    setActivePreset(null)
  }

  // ------ Render -----------------------------------------------------------
  const isLoading = locationsQuery.isLoading || pooQuery.isLoading
  const isError = locationsQuery.isError || pooQuery.isError
  const colorScheme = useComputedColorScheme('light', { getInitialValueInEffect: true })

  return (
    <Box style={{ height: 'calc(100vh - 52px)', display: 'flex', flexDirection: 'column' }}>
      {/* Controls bar */}
      <Paper
        shadow="xs"
        p="xs"
        style={{ zIndex: 1000, flexShrink: 0 }}
        data-testid="map-controls"
      >
        <Stack gap="xs">
          {/* Time-range row */}
          <Group gap="xs" align="flex-end" wrap="wrap">
            <TextInput
              label="From"
              type="datetime-local"
              value={startInput}
              onChange={(e) => setStartInput(e.currentTarget.value)}
              size="xs"
              style={{ minWidth: 180 }}
              data-testid="time-start-input"
            />
            <TextInput
              label="To"
              type="datetime-local"
              value={endInput}
              onChange={(e) => setEndInput(e.currentTarget.value)}
              size="xs"
              style={{ minWidth: 180 }}
              data-testid="time-end-input"
            />
            {/* Quick range + shift buttons (Grafana-style) — between To and Apply.
                zIndex raised above Leaflet (~1000) so the dropdown/tooltips are
                not painted over by the map below. */}
            <Group gap={4} align="flex-end">
              <Select
                label="Quick range"
                placeholder="Pick a range"
                data={TIME_PRESETS.map((p) => ({ value: p.value, label: p.label }))}
                value={activePreset}
                onChange={applyPreset}
                size="xs"
                allowDeselect={false}
                style={{ width: 150 }}
                comboboxProps={{ zIndex: 3000 }}
                data-testid="quick-range-select"
              />
              <Tooltip label="Shift earlier (one window back)" zIndex={3000}>
                <ActionIcon
                  variant="default"
                  size="input-xs"
                  aria-label="Shift earlier"
                  onClick={() => shiftWindow(-1)}
                  data-testid="shift-earlier"
                >
                  <ChevronLeft size={16} />
                </ActionIcon>
              </Tooltip>
              <Tooltip label="Shift later (one window forward)" zIndex={3000}>
                <ActionIcon
                  variant="default"
                  size="input-xs"
                  aria-label="Shift later"
                  onClick={() => shiftWindow(1)}
                  data-testid="shift-later"
                >
                  <ChevronRight size={16} />
                </ActionIcon>
              </Tooltip>
            </Group>

            <Button size="xs" onClick={applyWindow} data-testid="apply-window-button">
              Apply
            </Button>
            {isLoading && <Loader size="xs" />}
          </Group>

          {/* Layer toggles row */}
          <Group gap="md" wrap="wrap">
            <Switch
              label={
                <Group gap={4}>
                  <Text size="xs">Location heat</Text>
                  <Badge size="xs" color="blue" variant="light">
                    {locationsQuery.data?.length ?? 0}
                  </Badge>
                </Group>
              }
              checked={showLocationHeat}
              onChange={(e) => setShowLocationHeat(e.currentTarget.checked)}
              size="xs"
              data-testid="toggle-location-heat"
            />
            <Switch
              label={
                <Group gap={4}>
                  <Text size="xs">Poo heat</Text>
                  <Badge size="xs" color="orange" variant="light">
                    {filteredPoo.length}
                  </Badge>
                </Group>
              }
              checked={showPooHeat}
              onChange={(e) => setShowPooHeat(e.currentTarget.checked)}
              size="xs"
              data-testid="toggle-poo-heat"
            />
            <Switch
              label={<Text size="xs">Scatter (click to edit)</Text>}
              checked={showScatter}
              onChange={(e) => setShowScatter(e.currentTarget.checked)}
              size="xs"
              data-testid="toggle-scatter"
            />
          </Group>

          {/* Error banner */}
          {isError && (
            <Alert color="red" data-testid="map-error-alert">
              Failed to load data. Check connection and refresh.
            </Alert>
          )}
        </Stack>
      </Paper>

      {/* Map fills remaining height. `isolation: isolate` traps Leaflet's internal
          z-indexes (panes/controls up to ~1000) in their own stacking context so
          they can't paint over portaled popups (Quick-range dropdown, tooltips,
          and the point-select edit/delete modals). */}
      <Box style={{ flex: 1, minHeight: 0, isolation: 'isolate' }}>
        <RecordsMap
          locationHeatPoints={locationHeatPoints}
          pooHeatPoints={pooHeatPoints}
          locationScatterPoints={locationScatterPoints}
          pooScatterPoints={pooScatterPoints}
          showLocationHeat={showLocationHeat}
          showPooHeat={showPooHeat}
          showScatter={showScatter}
          onSelectLocation={handleSelectLocation}
          onSelectPoo={handleSelectPoo}
          height="100%"
          dark={colorScheme === 'dark'}
        />
      </Box>

      {/* ---------- Point-select modals ---------- */}

      {selection.kind === 'editLocation' && (
        <EditLocationModal
          record={selection.record}
          onClose={() => setSelection({ kind: 'none' })}
          onSaved={() => setSelection({ kind: 'none' })}
        />
      )}

      {selection.kind === 'deleteLocation' && (
        <ConfirmDeleteModal
          message={`Delete location record for ${selection.record.person} at ${selection.record.datetime}?`}
          loading={deleteLocationMut.isPending}
          onConfirm={async () => {
            await deleteLocationMut.mutateAsync({
              person: selection.record.person,
              datetime: selection.record.datetime,
            })
            setSelection({ kind: 'none' })
          }}
          onCancel={() => setSelection({ kind: 'none' })}
        />
      )}

      {selection.kind === 'editPoo' && (
        <EditPooModal
          record={selection.record}
          onClose={() => setSelection({ kind: 'none' })}
          onSaved={() => {
            // After saving, optionally switch to delete prompt or just close.
            setSelection({ kind: 'none' })
          }}
        />
      )}

      {selection.kind === 'deletePoo' && (
        <ConfirmDeleteModal
          message={`Delete poo record at ${selection.record.timestamp}?`}
          loading={deletePooMut.isPending}
          onConfirm={async () => {
            await deletePooMut.mutateAsync(selection.record.timestamp)
            setSelection({ kind: 'none' })
          }}
          onCancel={() => setSelection({ kind: 'none' })}
        />
      )}
    </Box>
  )
}
