import type { WidgetRegistryMeta } from 'officerdev';
import { useCallback, useRef, useState } from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useClient } from 'hooks/useClient';
import { useAuth } from 'hooks/useAuth';
import { CloudSun, MapPin, Settings } from 'lucide-react';
import { Widget } from '../Widget';
import { getWeatherInfo } from './weather-codes';

type WeatherLocation = {
  latitude: number;
  longitude: number;
  name: string;
};

type GeocodingResult = {
  id: number;
  name: string;
  country: string;
  admin1?: string;
  latitude: number;
  longitude: number;
};

type CurrentWeather = {
  temperature_2m: number;
  apparent_temperature: number;
  weather_code: number;
  wind_speed_10m: number;
  relative_humidity_2m: number;
  is_day: number;
};

type DailyWeather = {
  time: string[];
  temperature_2m_max: number[];
  temperature_2m_min: number[];
  weather_code: number[];
};

type ForecastResponse = {
  current: CurrentWeather;
  daily: DailyWeather;
};

const USER_STATE_KEY = ['USER_STATE'];
const LOCATION_STATE_KEY = 'weather-location';

function useWeatherLocation() {
  const client = useClient();
  const { isAuthenticated } = useAuth();
  const queryClient = useQueryClient();
  const clientRef = useRef(client);
  clientRef.current = client;

  const { data: state = {} } = useQuery<Record<string, unknown>>({
    queryKey: USER_STATE_KEY,
    enabled: isAuthenticated,
    queryFn: () => client.get('/user/state'),
    staleTime: Infinity,
  });

  const location = (state[LOCATION_STATE_KEY] as WeatherLocation | undefined) ?? null;

  const setLocation = useCallback(
    (loc: WeatherLocation | null) => {
      const currentState = queryClient.getQueryData<Record<string, unknown>>(USER_STATE_KEY) ?? {};
      queryClient.setQueryData(USER_STATE_KEY, { ...currentState, [LOCATION_STATE_KEY]: loc });
      clientRef.current.patch('/user/state', { [LOCATION_STATE_KEY]: loc }).catch(() => {});
    },
    [queryClient],
  );

  return [location, setLocation] as const;
}

const LocationSetup = ({ onSelect }: { onSelect: (loc: WeatherLocation) => void }) => {
  const [query, setQuery] = useState('');
  const [geoLoading, setGeoLoading] = useState(false);

  const { data: results = [] } = useQuery<GeocodingResult[]>({
    queryKey: ['geocoding', query],
    enabled: query.length >= 2,
    queryFn: async () => {
      const res = await fetch(`https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(query)}&count=5`);
      const data = await res.json();
      return data.results ?? [];
    },
    staleTime: 60_000,
  });

  const useGeolocation = () => {
    if (!navigator.geolocation) return;
    setGeoLoading(true);
    navigator.geolocation.getCurrentPosition(
      async (pos) => {
        const { latitude, longitude } = pos.coords;
        try {
          const res = await fetch(
            `https://geocoding-api.open-meteo.com/v1/search?name=${latitude.toFixed(2)},${longitude.toFixed(2)}&count=1`,
          );
          const data = await res.json();
          const name = data.results?.[0]?.name ?? `${latitude.toFixed(2)}, ${longitude.toFixed(2)}`;
          onSelect({ latitude, longitude, name });
        } catch {
          onSelect({ latitude, longitude, name: `${latitude.toFixed(2)}, ${longitude.toFixed(2)}` });
        } finally {
          setGeoLoading(false);
        }
      },
      () => setGeoLoading(false),
    );
  };

  return (
    <div className="flex flex-col gap-3 px-4 pb-4">
      <button
        type="button"
        className="flex items-center justify-center gap-2 rounded-md border border-border px-3 py-2 text-sm hover:bg-accent transition-colors cursor-pointer disabled:opacity-50"
        onClick={useGeolocation}
        disabled={geoLoading}
      >
        <MapPin size={14} />
        {geoLoading ? 'Locating...' : 'Use my location'}
      </button>
      <div className="relative">
        <input
          type="text"
          placeholder="Search city..."
          value={query}
          onChange={(e) => setQuery(e.target.value)}
          className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm outline-none focus:ring-1 focus:ring-ring"
        />
        {results.length > 0 && (
          <div className="absolute top-full left-0 z-10 mt-1 w-full rounded-md border border-border bg-popover shadow-md">
            {results.map((r) => (
              <button
                key={r.id}
                type="button"
                className="flex w-full items-center gap-2 px-3 py-2 text-left text-sm hover:bg-accent transition-colors cursor-pointer"
                onClick={() => onSelect({ latitude: r.latitude, longitude: r.longitude, name: r.name })}
              >
                <MapPin size={12} className="shrink-0 text-muted-foreground" />
                <span>
                  {r.name}
                  {r.admin1 ? `, ${r.admin1}` : ''}, {r.country}
                </span>
              </button>
            ))}
          </div>
        )}
      </div>
    </div>
  );
};

const dayName = (dateStr: string) => {
  const d = new Date(dateStr + 'T00:00:00');
  return d.toLocaleDateString(undefined, { weekday: 'short' });
};

const WeatherDisplay = ({ location, onReset }: { location: WeatherLocation; onReset: () => void }) => {
  const { data, isLoading } = useQuery<ForecastResponse>({
    queryKey: ['weather', location.latitude, location.longitude],
    queryFn: async () => {
      const params = new URLSearchParams({
        latitude: String(location.latitude),
        longitude: String(location.longitude),
        current: 'temperature_2m,apparent_temperature,weather_code,wind_speed_10m,relative_humidity_2m,is_day',
        daily: 'temperature_2m_max,temperature_2m_min,weather_code',
        timezone: 'auto',
        forecast_days: '5',
      });
      const res = await fetch(`https://api.open-meteo.com/v1/forecast?${params}`);
      return res.json();
    },
    staleTime: 15 * 60_000,
  });

  if (isLoading || !data) {
    return (
      <div className="flex flex-col gap-3 px-4 pb-4">
        <div className="h-16 animate-pulse rounded bg-muted" />
        <div className="flex gap-2">
          {Array.from({ length: 5 }).map((_, i) => (
            <div key={i} className="h-16 flex-1 animate-pulse rounded bg-muted" />
          ))}
        </div>
      </div>
    );
  }

  const { current, daily } = data;
  const info = getWeatherInfo(current.weather_code, current.is_day === 1);

  return (
    <div className="flex flex-col gap-3 px-4 pb-4">
      <div className="flex items-start justify-between">
        <div className="flex items-center gap-3">
          <span className="text-3xl">{info.icon}</span>
          <div>
            <div className="text-2xl font-bold tabular-nums">{Math.round(current.temperature_2m)}°C</div>
            <div className="text-xs text-muted-foreground">{info.label}</div>
          </div>
        </div>
        <button
          type="button"
          className="p-1 rounded cursor-pointer text-muted-foreground hover:text-foreground"
          onClick={onReset}
        >
          <Settings size={14} />
        </button>
      </div>

      <div className="flex gap-3 text-xs text-muted-foreground">
        <span>Feels {Math.round(current.apparent_temperature)}°C</span>
        <span>💧 {current.relative_humidity_2m}%</span>
        <span>💨 {Math.round(current.wind_speed_10m)} km/h</span>
      </div>

      <div className="flex items-center gap-1 text-xs text-muted-foreground">
        <MapPin size={10} />
        <span>{location.name}</span>
      </div>

      <div className="flex gap-1">
        {daily.time.map((date, i) => {
          const dayInfo = getWeatherInfo(daily.weather_code[i]!, true);
          return (
            <div key={date} className="flex flex-1 flex-col items-center gap-0.5 rounded-md bg-muted/50 py-1.5">
              <span className="text-[10px] font-medium text-muted-foreground">{dayName(date)}</span>
              <span className="text-sm">{dayInfo.icon}</span>
              <span className="text-[10px] tabular-nums">
                {Math.round(daily.temperature_2m_max[i]!)}° / {Math.round(daily.temperature_2m_min[i]!)}°
              </span>
            </div>
          );
        })}
      </div>
    </div>
  );
};

export const Weather = () => {
  const [location, setLocation] = useWeatherLocation();
  const [showSetup, setShowSetup] = useState(false);

  if (!location || showSetup) {
    return (
      <Widget title="Weather">
        <LocationSetup
          onSelect={(loc) => {
            setLocation(loc);
            setShowSetup(false);
          }}
        />
      </Widget>
    );
  }

  return (
    <Widget title="Weather">
      <WeatherDisplay
        location={location}
        onReset={() => setShowSetup(true)}
      />
    </Widget>
  );
};

export const widgetRegistryMetas: WidgetRegistryMeta[] = [
  { key: 'weather', name: 'Weather', icon: CloudSun, component: Weather },
];
