import { create } from 'zustand';
import axios from 'axios';
import { message } from 'antd';
import { api } from '@/lib/api';
import type {
  CanvassAddress,
  AddressGroup,
  WalkingRoute,
  CanvassSession,
  RecordVisitPayload,
  BulkRecordVisitPayload,
  CanvassVisit,
  CanvassLocation,
} from '@/types/canvass';
import type { PublicCut, MapSettings, ReverseGeocodeResult, GeocodeResult } from '@/types/api';

interface CanvassState {
  // Mode
  mode: 'free' | 'session';
  activeCutId: string | null;

  // Session
  session: CanvassSession | null;
  sessionLoading: boolean;

  // Addresses (replaces locations)
  addresses: CanvassAddress[];
  addressGroups: AddressGroup[]; // Grouped by building
  addressesLoading: boolean;

  // Cuts & map settings
  cuts: PublicCut[];
  mapSettings: MapSettings | null;

  // Walking route
  route: WalkingRoute | null;
  routeVisible: boolean;

  // Selected address
  selectedAddressId: string | null;

  // GPS
  gpsFollowing: boolean;

  // Actions — data loading
  loadAllAddresses: (bounds?: { minLat: number; maxLat: number; minLng: number; maxLng: number }, limit?: number) => Promise<void>;
  loadCuts: () => Promise<void>;
  loadMapSettings: () => Promise<void>;
  loadSession: () => Promise<void>;
  loadRoute: (cutId: string, excludeVisited?: boolean, lat?: number, lng?: number) => Promise<void>;

  // Actions — session management
  enterSessionMode: (cutId: string, shiftId?: string, lat?: number, lng?: number) => Promise<void>;
  exitSessionMode: () => Promise<void>;
  startSession: (cutId: string, shiftId?: string, lat?: number, lng?: number) => Promise<CanvassSession>;
  endSession: () => Promise<void>;

  // Actions — address management
  loadAddresses: (cutId: string, bounds?: { minLat: number; maxLat: number; minLng: number; maxLng: number }, limit?: number) => Promise<void>;
  recordVisit: (payload: RecordVisitPayload) => Promise<CanvassVisit>;
  recordBulkVisit: (payload: BulkRecordVisitPayload) => Promise<{ created: number; visits: CanvassVisit[] }>;
  updateAddressFields: (id: string, data: Record<string, unknown>) => Promise<void>;
  addLocation: (data: Record<string, unknown>) => Promise<CanvassLocation>; // Still creates location
  reverseGeocode: (lat: number, lng: number) => Promise<ReverseGeocodeResult>;
  geocodeSearch: (address: string) => Promise<GeocodeResult>;

  // Actions — navigation
  goToNextUnitInBuilding: () => void;

  // Actions — UI
  selectAddress: (id: string | null) => void;
  toggleRoute: () => void;
  setGpsFollowing: (v: boolean) => void;
  reset: () => void;
}

// Helper function to group addresses by location
// Memoized grouping to avoid re-sorting unchanged groups
function groupAddressesByLocation(
  addresses: CanvassAddress[],
  previousGroups?: AddressGroup[]
): AddressGroup[] {
  const groups = new Map<string, CanvassAddress[]>();

  // Create map of location ID → addresses
  for (const addr of addresses) {
    const locId = addr.location.id;
    if (!groups.has(locId)) {
      groups.set(locId, []);
    }
    groups.get(locId)!.push(addr);
  }

  // Build address groups, reusing previous sorted arrays when possible
  const previousGroupMap = new Map(
    previousGroups?.map(g => [g.locationId, g]) || []
  );

  return Array.from(groups.entries()).map(([locationId, addrs]) => {
    const prevGroup = previousGroupMap.get(locationId);

    // If group unchanged (same address IDs), reuse previous group
    if (prevGroup && addrs.length === prevGroup.addresses.length) {
      const prevIds = new Set(prevGroup.addresses.map(a => a.id));
      const currIds = new Set(addrs.map(a => a.id));
      if ([...currIds].every(id => prevIds.has(id))) {
        // Update visit data but keep sort order
        return {
          ...prevGroup,
          addresses: prevGroup.addresses.map(prevAddr => {
            const currAddr = addrs.find(a => a.id === prevAddr.id);
            return currAddr || prevAddr;
          }),
        };
      }
    }

    // Need to sort (new addresses or order changed)
    const sorted = addrs.sort((a, b) => {
      if (!a.unitNumber) return -1;
      if (!b.unitNumber) return 1;
      const aNum = parseInt(a.unitNumber);
      const bNum = parseInt(b.unitNumber);
      if (isNaN(aNum)) return 1;
      if (isNaN(bNum)) return -1;
      return aNum - bNum;
    });

    const first = sorted[0]!;
    return {
      locationId,
      latitude: first.location.latitude,
      longitude: first.location.longitude,
      baseAddress: first.location.address,
      buildingNotes: first.location.buildingNotes,
      addresses: sorted,
      isMultiUnit: sorted.length > 1,
    };
  });
}

export const useCanvassStore = create<CanvassState>((set, get) => ({
  mode: 'free',
  activeCutId: null,
  session: null,
  sessionLoading: false,
  addresses: [],
  addressGroups: [],
  addressesLoading: false,
  cuts: [],
  mapSettings: null,
  route: null,
  routeVisible: false,
  selectedAddressId: null,
  gpsFollowing: true,

  // ─── Data Loading ──────────────────────────────────────────────────

  loadAllAddresses: async (bounds?: {
    minLat: number;
    maxLat: number;
    minLng: number;
    maxLng: number;
  }, limit?: number) => {
    set({ addressesLoading: true });
    try {
      const params = new URLSearchParams();
      if (bounds) {
        params.set('minLat', bounds.minLat.toString());
        params.set('maxLat', bounds.maxLat.toString());
        params.set('minLng', bounds.minLng.toString());
        params.set('maxLng', bounds.maxLng.toString());
      }
      if (limit) {
        params.set('limit', limit.toString());
      }
      const url = `/map/canvass/locations${params.toString() ? `?${params}` : ''}`;
      const { data } = await api.get(url);
      const addresses = data as CanvassAddress[];
      const groups = groupAddressesByLocation(addresses);
      set({ addresses, addressGroups: groups });
    } finally {
      set({ addressesLoading: false });
    }
  },

  loadCuts: async () => {
    try {
      const { data } = await axios.get('/api/map/cuts/public');
      set({ cuts: data });
    } catch {
      // Non-critical — cuts overlay just won't show
    }
  },

  loadMapSettings: async () => {
    try {
      const { data } = await axios.get('/api/map/settings');
      set({ mapSettings: data });
    } catch {
      // Non-critical — use default center/zoom
    }
  },

  loadSession: async () => {
    set({ sessionLoading: true });
    try {
      const { data } = await api.get('/map/canvass/my/session');
      if (data) {
        set({ session: data, mode: 'session', activeCutId: data.cutId });
      } else {
        set({ session: null });
      }
    } catch {
      set({ session: null });
    } finally {
      set({ sessionLoading: false });
    }
  },

  loadAddresses: async (
    cutId,
    bounds?: {
      minLat: number;
      maxLat: number;
      minLng: number;
      maxLng: number;
    },
    limit?: number
  ) => {
    set({ addressesLoading: true });
    try {
      const params = new URLSearchParams();
      if (bounds) {
        params.set('minLat', bounds.minLat.toString());
        params.set('maxLat', bounds.maxLat.toString());
        params.set('minLng', bounds.minLng.toString());
        params.set('maxLng', bounds.maxLng.toString());
      }
      if (limit) {
        params.set('limit', limit.toString());
      }
      const url = `/map/canvass/cuts/${cutId}/locations${params.toString() ? `?${params}` : ''}`;
      const { data } = await api.get(url);
      const addresses = data as CanvassAddress[];
      const groups = groupAddressesByLocation(addresses);
      set({ addresses, addressGroups: groups });
    } finally {
      set({ addressesLoading: false });
    }
  },

  loadRoute: async (cutId, excludeVisited = true, lat, lng) => {
    const params = new URLSearchParams();
    if (excludeVisited) params.set('excludeVisited', 'true');
    if (lat !== undefined) params.set('startLatitude', lat.toString());
    if (lng !== undefined) params.set('startLongitude', lng.toString());
    const { data } = await api.get(`/map/canvass/cuts/${cutId}/route?${params}`);
    set({ route: data });
  },

  // ─── Session Management ────────────────────────────────────────────

  enterSessionMode: async (cutId, shiftId, lat, lng) => {
    const { session } = get();
    // End any existing session for a different cut
    if (session && session.cutId !== cutId) {
      await api.post(`/map/canvass/sessions/${session.id}/end`);
    }

    // Start new session if none or different cut
    if (!session || session.cutId !== cutId) {
      const { data } = await api.post('/map/canvass/sessions', {
        cutId,
        shiftId,
        startLatitude: lat,
        startLongitude: lng,
      });
      set({ session: data });
    }

    // Load cut-filtered addresses
    set({ addressesLoading: true, mode: 'session', activeCutId: cutId });
    try {
      const { data } = await api.get(`/map/canvass/cuts/${cutId}/locations`);
      const addresses = data as CanvassAddress[];
      const groups = groupAddressesByLocation(addresses);
      set({ addresses, addressGroups: groups });
    } finally {
      set({ addressesLoading: false });
    }
  },

  exitSessionMode: async () => {
    const { session } = get();
    if (session) {
      await api.post(`/map/canvass/sessions/${session.id}/end`);
    }
    set({
      session: null,
      mode: 'free',
      activeCutId: null,
      route: null,
      routeVisible: false,
    });
    // Reload all addresses
    await get().loadAllAddresses();
  },

  startSession: async (cutId, shiftId, lat, lng) => {
    const { data } = await api.post('/map/canvass/sessions', {
      cutId,
      shiftId,
      startLatitude: lat,
      startLongitude: lng,
    });
    set({ session: data });
    return data;
  },

  endSession: async () => {
    const { session } = get();
    if (!session) return;
    await api.post(`/map/canvass/sessions/${session.id}/end`);
    set({ session: null });
  },

  // ─── Address Management ────────────────────────────────────────────

  recordVisit: async (payload) => {
    const { data } = await api.post('/map/canvass/visits', payload);

    // Optimistic update: mark the address as visited in local state
    set((state) => {
      const updatedAddresses = state.addresses.map((addr) =>
        addr.id === payload.addressId
          ? {
              ...addr,
              lastVisit: {
                outcome: payload.outcome,
                visitedAt: new Date().toISOString(),
                visitorName: null,
                isMyVisit: true,
              },
              supportLevel: payload.supportLevel || addr.supportLevel,
              sign: payload.signRequested !== undefined ? payload.signRequested : addr.sign,
              signSize: payload.signSize || addr.signSize,
            }
          : addr
      );

      return {
        addresses: updatedAddresses,
        addressGroups: groupAddressesByLocation(updatedAddresses, state.addressGroups),  // Pass previous groups
        selectedAddressId: null,
      };
    });

    return data;
  },

  recordBulkVisit: async (payload) => {
    const { data } = await api.post('/map/canvass/visits/bulk', payload);

    message.success(`Recorded ${data.created} visits`);

    // Reload addresses to reflect changes
    const { activeCutId } = get();
    if (activeCutId) {
      await get().loadAddresses(activeCutId);
    } else {
      await get().loadAllAddresses();
    }

    return data;
  },

  updateAddressFields: async (id, data) => {
    await api.put(`/map/canvass/locations/${id}`, data);

    // Optimistic update in addresses array
    set((state) => {
      const updatedAddresses = state.addresses.map((addr) =>
        addr.id === id
          ? {
              ...addr,
              ...data,
              // Keep lastVisit, location from existing
            }
          : addr
      );

      return {
        addresses: updatedAddresses,
        addressGroups: groupAddressesByLocation(updatedAddresses, state.addressGroups),
      };
    });
  },

  goToNextUnitInBuilding: () => {
    const { selectedAddressId, addressGroups } = get();
    if (!selectedAddressId) return;

    // Find current address's building
    const currentGroup = addressGroups.find((g) =>
      g.addresses.some((a) => a.id === selectedAddressId)
    );

    if (!currentGroup) return;

    // Find next unvisited unit in same building
    const currentIndex = currentGroup.addresses.findIndex((a) => a.id === selectedAddressId);
    const nextUnvisited = currentGroup.addresses
      .slice(currentIndex + 1)
      .find((a) => !a.lastVisit);

    if (nextUnvisited) {
      set({ selectedAddressId: nextUnvisited.id });
    } else {
      message.info('No more unvisited units in this building');
    }
  },

  addLocation: async (data) => {
    const { data: newLoc } = await api.post('/map/canvass/locations', data);

    // Build a CanvassLocation from the response
    const canvassLoc: CanvassLocation = {
      id: newLoc.id,
      latitude: Number(newLoc.latitude),
      longitude: Number(newLoc.longitude),
      address: newLoc.address,
      unitNumber: newLoc.unitNumber,
      firstName: newLoc.firstName,
      lastName: newLoc.lastName,
      supportLevel: newLoc.supportLevel,
      sign: newLoc.sign,
      signSize: newLoc.signSize,
      notes: newLoc.notes,
      lastVisit: null,
    };

    // Note: State update removed - callers should refresh data after adding location
    return canvassLoc;
  },

  reverseGeocode: async (lat, lng) => {
    const { data } = await api.post('/map/canvass/reverse-geocode', {
      latitude: lat,
      longitude: lng,
    });
    return data;
  },

  geocodeSearch: async (address) => {
    const { data } = await api.post('/map/canvass/geocode-search', { address });
    return data;
  },

  // ─── UI ────────────────────────────────────────────────────────────

  selectAddress: (id) => set({ selectedAddressId: id }),

  toggleRoute: () => set((state) => ({ routeVisible: !state.routeVisible })),

  setGpsFollowing: (v) => set({ gpsFollowing: v }),

  reset: () =>
    set({
      mode: 'free',
      activeCutId: null,
      session: null,
      sessionLoading: false,
      addresses: [],
      addressGroups: [],
      addressesLoading: false,
      cuts: [],
      mapSettings: null,
      route: null,
      routeVisible: false,
      selectedAddressId: null,
      gpsFollowing: true,
    }),
}));
