import { useMap, Marker, Popup } from 'react-leaflet';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import Autocomplete from '@mui/material/Autocomplete';
import TextField from '@mui/material/TextField';
import matchSorter, { rankings } from 'match-sorter';
import L from 'leaflet';
import iconRetinaUrl from 'leaflet/dist/images/marker-icon-2x.png';
import iconUrl from 'leaflet/dist/images/marker-icon.png';
import shadowUrl from 'leaflet/dist/images/marker-shadow.png';

const addressSearchStyle = {
  panel: {
    width: 380,
    maxWidth: '92vw',
  },
  root: {
    composes: 'map-overlay top-right',
  } as any,
};

type Dya = {
  id: string | number;
  latitude: number;
  longitude: number;
  dya_text: string;
  agb_dyncol: string | null;
  html_info: string | null;
  type: string;
};

type LocalSuggestion = {
  id: string;
  lat: number;
  lon: number;
  display_name: string;
  kind: 'street' | 'address' | 'place';
  searchKey: string;
};

type OverpassElement = {
  id: number;
  type: 'node' | 'way' | 'relation';
  lat?: number;
  lon?: number;
  center?: { lat: number; lon: number };
  tags?: Record<string, string>;
};

type OverpassResponse = { elements: OverpassElement[] };

const MAX_ITEMS = 8000;

function bboxFromDyas(dyas: Dya[]) {
  if (!dyas.length) return null;
  let s = Infinity,
    w = Infinity,
    n = -Infinity,
    e = -Infinity;
  for (const d of dyas) {
    s = Math.min(s, d.latitude);
    n = Math.max(n, d.latitude);
    w = Math.min(w, d.longitude);
    e = Math.max(e, d.longitude);
  }
  const pad = 0.02;
  return [s - pad, w - pad, n + pad, e + pad] as const;
}

function str(v?: string) {
  return v ? v.trim() : '';
}

function makeAddressLabel(tags: Record<string, string>) {
  const street = str(tags['addr:street']);
  const hno = str(tags['addr:housenumber']);
  const city = str(
    tags['addr:city'] || tags['addr:town'] || tags['is_in:city'],
  );
  const label = hno ? `${street} ${hno}` : street;
  return city ? `${label}, ${city}` : label;
}

function makeStreetLabel(tags: Record<string, string>) {
  const name = str(tags['name']);
  const city = str(
    tags['addr:city'] || tags['addr:town'] || tags['is_in:city'],
  );
  return city ? `${name}, ${city}` : name;
}

async function fetchOverpass(
  bbox: readonly [number, number, number, number],
): Promise<LocalSuggestion[]> {
  const [s, w, n, e] = bbox;
  const query = `
[out:json][timeout:25];
(
  way["highway"]["name"](${s},${w},${n},${e});
  node["addr:housenumber"]["addr:street"](${s},${w},${n},${e});
  way["addr:housenumber"]["addr:street"](${s},${w},${n},${e});
  node["place"]["name"](${s},${w},${n},${e});
);
out tags center;`.trim();
  const res = await fetch('https://overpass-api.de/api/interpreter', {
    method: 'POST',
    body: query,
    headers: { 'Content-Type': 'text/plain;charset=UTF-8' },
  });
  if (!res.ok) throw new Error(`Overpass ${res.status}`);
  const data = (await res.json()) as OverpassResponse;
  const out: LocalSuggestion[] = [];
  const seen = new Set<string>();
  for (const el of data.elements) {
    const tags = el.tags || {};
    if (el.type === 'way' && tags.name && el.center) {
      const label = makeStreetLabel(tags);
      if (!label) continue;
      const key = `street|${label}`;
      if (seen.has(key)) continue;
      out.push({
        id: `${el.type}:${el.id}`,
        lat: el.center.lat,
        lon: el.center.lon,
        display_name: label,
        kind: 'street',
        searchKey: `${label}`.toLowerCase(),
      });
      seen.add(key);
    } else if (tags['addr:street'] && tags['addr:housenumber']) {
      const label = makeAddressLabel(tags);
      if (!label) continue;
      const lat = el.lat ?? el.center?.lat;
      const lon = el.lon ?? el.center?.lon;
      if (lat == null || lon == null) continue;
      const key = `addr|${label}`;
      if (seen.has(key)) continue;
      out.push({
        id: `${el.type}:${el.id}`,
        lat,
        lon,
        display_name: label,
        kind: 'address',
        searchKey: `${label}`.toLowerCase(),
      });
      seen.add(key);
    } else if (
      el.type === 'node' &&
      tags['place'] &&
      tags['name'] &&
      el.lat &&
      el.lon
    ) {
      const label = str(tags['name']);
      if (!label) continue;
      const key = `place|${label}`;
      if (seen.has(key)) continue;
      out.push({
        id: `${el.type}:${el.id}`,
        lat: el.lat,
        lon: el.lon,
        display_name: label,
        kind: 'place',
        searchKey: label.toLowerCase(),
      });
      seen.add(key);
    }
    if (out.length >= MAX_ITEMS) break;
  }
  out.sort((a, b) => {
    const rank = (k: LocalSuggestion['kind']) =>
      k === 'address' ? 0 : k === 'street' ? 1 : 2;
    const dr = rank(a.kind) - rank(b.kind);
    if (dr) return dr;
    return a.display_name.localeCompare(b.display_name, 'de');
  });
  return out;
}

const AddressSearch: React.FC<{ dyas: Dya[] }> = ({ dyas }) => {
  const map = useMap();
  useEffect(() => {
    L.Icon.Default.mergeOptions({ iconRetinaUrl, iconUrl, shadowUrl });
  }, []);
  const [selected, setSelected] = useState<{
    lat: number;
    lon: number;
    label: string;
  } | null>(null);
  const [allOptions, setAllOptions] = useState<LocalSuggestion[]>([]);
  const [inputValue, setInputValue] = useState('');
  const [loading, setLoading] = useState(false);
  const bboxSigRef = useRef<string | null>(null);

  useEffect(() => {
    const bbox = bboxFromDyas(dyas);
    if (!bbox) return;
    const sig = bbox.map((n) => n.toFixed(3)).join(',');
    if (bboxSigRef.current === sig && allOptions.length) return;
    setLoading(true);
    fetchOverpass(bbox)
      .then((items) => {
        bboxSigRef.current = sig;
        setAllOptions(items);
      })
      .catch(() => {})
      .finally(() => setLoading(false));
  }, [dyas, allOptions.length]);

  const filtered = useMemo(() => {
    const q = inputValue.trim().toLowerCase();
    if (!q) return allOptions.slice(0, 50);
    const hasNumber = /\d/.test(q);
    return matchSorter(allOptions, q, {
      keys: ['searchKey'],
      threshold: hasNumber ? rankings.CONTAINS : rankings.WORD_STARTS_WITH,
    }).slice(0, 60);
  }, [allOptions, inputValue]);

  const handleInputChange = (_: unknown, value: string) => setInputValue(value);

  const handleOptionSelected = (_: unknown, value: LocalSuggestion | null) => {
    if (!value) return;
    const z = value.kind === 'address' ? 18 : value.kind === 'street' ? 15 : 13;
    setSelected({ lat: value.lat, lon: value.lon, label: value.display_name });
    map.flyTo([value.lat, value.lon], z);
  };

  const onKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === 'Enter' && filtered.length) {
      const top = filtered[0];
      const z = top.kind === 'address' ? 18 : top.kind === 'street' ? 15 : 13;
      setSelected({ lat: top.lat, lon: top.lon, label: top.display_name });
      map.flyTo([top.lat, top.lon], z);
    }
  };

  return (
    <>
      {selected && (
        <Marker position={[selected.lat, selected.lon]}>
          <Popup>{selected.label}</Popup>
        </Marker>
      )}
      <div
        className='map-overlay top-right overlay-card'
        style={{ padding: 8 }}
      >
        <div style={addressSearchStyle.panel}>
          <Autocomplete
            options={filtered}
            getOptionLabel={(o) => o.display_name}
            loading={loading}
            inputValue={inputValue}
            onInputChange={handleInputChange}
            onChange={handleOptionSelected}
            filterOptions={(x) => x}
            renderInput={(params) => (
              <TextField
                {...params}
                label='Adresse/Strasse (lokal, hausnummernfähig)'
                variant='outlined'
                size='small'
                onKeyDown={onKeyDown}
                placeholder={
                  loading ? 'Lade Adressen…' : 'z. B. Musterstraße 12'
                }
                fullWidth
              />
            )}
            renderOption={(option) => (
              <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
                <span
                  style={{
                    fontSize: 11,
                    opacity: 0.65,
                    border: '1px solid #ddd',
                    borderRadius: 4,
                    padding: '1px 4px',
                    minWidth: 60,
                    textAlign: 'center',
                    textTransform: 'capitalize',
                  }}
                >
                  {option.kind}
                </span>
                <span>{option.display_name}</span>
              </div>
            )}
          />
          <div
            style={{
              fontSize: 11,
              color: '#666',
              marginTop: 6,
              textAlign: 'right',
            }}
          >
            Daten: © OpenStreetMap, Overpass API
          </div>
        </div>
      </div>
    </>
  );
};

export default AddressSearch;
