import { act, renderHook } from '@testing-library/react';
import { describe, expect, it } from 'vitest';

import type { POI } from '../types';
import { usePoiLayers } from './usePoiLayers';

const supermarket: POI = {
  id: 'poi-1',
  name: 'Market Hall',
  category: 'Supermarket',
  group: 'Groceries',
  lat: 51.5,
  lng: -0.12,
  emoji: '🛒',
};

const waitrose: POI = {
  id: 'poi-3',
  name: 'Waitrose Marylebone',
  category: 'Waitrose',
  group: 'Groceries',
  lat: 51.52,
  lng: -0.15,
  emoji: '🛒',
};

const busStop: POI = {
  id: 'poi-2',
  name: 'High Street Stop',
  category: 'Bus stop',
  group: 'Public Transport',
  lat: 51.501,
  lng: -0.121,
  emoji: '🚌',
};

const foodWarehouse: POI = {
  id: 'poi-4',
  name: 'Iceland Avonmead Food Warehouse',
  category: 'Iceland',
  icon_category: 'The Food Warehouse',
  group: 'Groceries',
  lat: 51.49,
  lng: -0.18,
  emoji: '🛒',
};

function layerById(layers: readonly unknown[], id: string) {
  const layer = layers.find((item) => (item as { id?: string }).id === id);
  if (!layer) throw new Error(`Layer ${id} not found`);
  return layer as { props: Record<string, unknown> };
}

type PoiIconDef = { url: string; width: number; height: number };
type PoiColor = [number, number, number, number];

describe('usePoiLayers', () => {
  it('returns the expected layer stack', () => {
    const { result } = renderHook(() =>
      usePoiLayers({ pois: [supermarket], zoom: 15, isDark: false })
    );

    expect(result.current.poiLayers.map((layer) => layer.id)).toEqual([
      'poi-shadow',
      'poi-background',
      'poi-icons',
      'poi-clusters',
      'poi-cluster-text',
    ]);
  });

  it('uses POI category logos for map marker icons', () => {
    const { result } = renderHook(() =>
      usePoiLayers({ pois: [waitrose], zoom: 15, isDark: false })
    );
    const iconLayer = layerById(result.current.poiLayers, 'poi-icons');
    const getIcon = iconLayer.props.getIcon as (poi: POI) => PoiIconDef;
    const getSize = iconLayer.props.getSize as (poi: POI) => number;

    expect(getIcon(waitrose)).toMatchObject({
      url: '/assets/poi-icons/logos/waitrose.svg',
      width: 96,
      height: 48,
    });
    expect(getSize(waitrose)).toBe(14);
  });

  it('prefers POI fascia icon categories for map marker icons', () => {
    const { result } = renderHook(() =>
      usePoiLayers({ pois: [foodWarehouse], zoom: 15, isDark: false })
    );
    const iconLayer = layerById(result.current.poiLayers, 'poi-icons');
    const getIcon = iconLayer.props.getIcon as (poi: POI) => PoiIconDef;
    const getSize = iconLayer.props.getSize as (poi: POI) => number;

    expect(getIcon(foodWarehouse)).toMatchObject({
      url: '/assets/poi-icons/logos/the_food_warehouse.png',
      width: 96,
      height: 48,
    });
    expect(getSize(foodWarehouse)).toBe(14);
  });

  it('keeps generic emoji POIs at the compact marker size', () => {
    const { result } = renderHook(() =>
      usePoiLayers({ pois: [supermarket], zoom: 15, isDark: false })
    );
    const iconLayer = layerById(result.current.poiLayers, 'poi-icons');
    const getIcon = iconLayer.props.getIcon as (poi: POI) => PoiIconDef;
    const getSize = iconLayer.props.getSize as (poi: POI) => number;

    expect(getIcon(supermarket)).toMatchObject({
      url: '/assets/twemoji/1f6d2.png',
      width: 72,
      height: 72,
    });
    expect(getSize(supermarket)).toBe(11);
  });

  it('hides the circular marker badge behind bundled logo icons', () => {
    const { result } = renderHook(() =>
      usePoiLayers({ pois: [waitrose, supermarket], zoom: 15, isDark: false })
    );
    const shadowLayer = layerById(result.current.poiLayers, 'poi-shadow');
    const backgroundLayer = layerById(result.current.poiLayers, 'poi-background');
    const getShadowRadius = shadowLayer.props.getRadius as (poi: POI) => number;
    const getBackgroundRadius = backgroundLayer.props.getRadius as (poi: POI) => number;
    const getFillColor = backgroundLayer.props.getFillColor as (poi: POI) => PoiColor;
    const getLineColor = backgroundLayer.props.getLineColor as (poi: POI) => PoiColor;

    expect(getShadowRadius(waitrose)).toBe(0);
    expect(getBackgroundRadius(waitrose)).toBe(14);
    expect(getFillColor(waitrose)).toEqual([0, 0, 0, 0]);
    expect(getLineColor(waitrose)).toEqual([0, 0, 0, 0]);

    expect(getShadowRadius(supermarket)).toBe(10);
    expect(getBackgroundRadius(supermarket)).toBe(8);
    expect(getFillColor(supermarket)).toEqual([255, 255, 255, 255]);
    expect(getLineColor(supermarket)).toEqual([34, 197, 94, 255]);
  });

  it('hides minor POI categories until the configured zoom threshold', () => {
    const { result, rerender } = renderHook(
      ({ zoom }) => usePoiLayers({ pois: [busStop], zoom, isDark: false }),
      { initialProps: { zoom: 13 } }
    );

    expect(layerById(result.current.poiLayers, 'poi-background').props.data).toEqual([]);
    expect(result.current.visiblePois).toEqual([]);

    rerender({ zoom: 14 });

    expect(layerById(result.current.poiLayers, 'poi-background').props.data).toEqual([busStop]);
    expect(result.current.visiblePois).toEqual([busStop]);
  });

  it('keeps POI hover popup state in sync with layer hover events', () => {
    const { result } = renderHook(() =>
      usePoiLayers({ pois: [supermarket], zoom: 15, isDark: false })
    );
    const backgroundLayer = layerById(result.current.poiLayers, 'poi-background');

    act(() => {
      (backgroundLayer.props.onHover as (info: unknown) => void)({
        object: supermarket,
        x: 42,
        y: 88,
      });
    });

    expect(result.current.popupInfo).toEqual({
      x: 42,
      y: 88,
      name: supermarket.name,
      category: supermarket.category,
      icon_category: undefined,
      group: supermarket.group,
      emoji: supermarket.emoji,
      id: supermarket.id,
    });

    act(() => {
      result.current.clearPopupInfo();
    });

    expect(result.current.popupInfo).toBeNull();
  });

  it('creates cluster hover popup state from clustered POIs', () => {
    const clusteredPois = Array.from(
      { length: 4 },
      (_, index): POI => ({
        ...supermarket,
        id: `clustered-${index}`,
        name: `Clustered ${index}`,
        lat: 51.5 + index * 0.0001,
        lng: -0.12 - index * 0.0001,
      })
    );
    const { result } = renderHook(() =>
      usePoiLayers({ pois: clusteredPois, zoom: 5, isDark: true })
    );
    const clusterLayer = layerById(result.current.poiLayers, 'poi-clusters');
    const clusters = clusterLayer.props.data as Array<{ count: number; lng: number; lat: number }>;

    expect(clusters).toHaveLength(1);
    expect(clusters[0].count).toBe(4);

    act(() => {
      (clusterLayer.props.onHover as (info: unknown) => void)({
        object: clusters[0],
        x: 12,
        y: 24,
      });
    });

    expect(result.current.popupInfo).toMatchObject({
      x: 12,
      y: 24,
      name: '4 places',
      isCluster: true,
      clusterCount: 4,
    });
  });
});
