/**
 * Mawidi Demo Scheduler - useAvailability Hook
 * Manages availability data fetching and time slot generation
 */

"use client";

import { useState, useEffect, useMemo, useCallback } from "react";
import type { BookingDate, TimeSlot } from "../types/scheduler.types";
import { SCHEDULER_CONFIG } from "../config/schedulerConfig";
import {
  isWithinBookingWindow,
  isBusinessDay,
  meetsMinimumAdvance,
} from "../utils/dateHelpers";

// Stable empty array reference to prevent infinite loops
// This is reused across all calls instead of creating new [] on each render
const EMPTY_BOOKINGS: Date[] = [];

export interface UseAvailabilityOptions {
  /** Meeting duration to check availability for */
  duration: 30 | 60;
  /** Timezone for scheduling */
  timezone?: string;
  /** Optional existing bookings to exclude */
  existingBookings?: Date[];
}

export interface UseAvailabilityReturn {
  /** Array of available dates within booking window */
  availableDates: BookingDate[];
  /** Loading state for availability check */
  loading: boolean;
  /** Loading state for specific date's time slots */
  loadingTimeSlots: boolean;
  /** Error if availability check failed */
  error: string | null;
  /** Get time slots for a specific date */
  getTimeSlotsForDate: (date: Date) => TimeSlot[];
  /** Fetch bookings for a specific date */
  fetchBookingsForDate: (date: Date) => Promise<void>;
  /** Refresh availability data */
  refreshAvailability: () => void;
}

/**
 * useAvailability Hook
 *
 * Manages availability data, generates time slots, and handles business logic
 * for working hours, booking windows, and minimum advance booking
 *
 * @example
 * ```tsx
 * const { availableDates, getTimeSlotsForDate, loading } = useAvailability({
 *   duration: 30,
 *   timezone: 'Asia/Riyadh'
 * });
 * ```
 */
export function useAvailability({
  duration,
  existingBookings = EMPTY_BOOKINGS, // Use stable reference instead of new []
}: UseAvailabilityOptions): UseAvailabilityReturn {
  const [availableDates, setAvailableDates] = useState<BookingDate[]>([]);
  const [loading, setLoading] = useState(true);
  const [loadingTimeSlots, setLoadingTimeSlots] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [refreshKey, setRefreshKey] = useState(0);
  const [fetchedBookings, setFetchedBookings] = useState<
    Map<string, Set<string>>
  >(new Map());

  // Convert existing bookings to Set for O(1) lookup
  const bookedSlots = useMemo(() => {
    const set = new Set<string>();
    existingBookings.forEach((booking) => {
      const key = `${booking.toISOString().split("T")[0]}_${booking.getHours()}:${String(booking.getMinutes()).padStart(2, "0")}`;
      set.add(key);
    });
    return set;
  }, [existingBookings]);

  /**
   * Generate time slots for a specific date
   */
  const generateTimeSlotsForDate = useCallback(
    (date: Date): TimeSlot[] => {
      const slots: TimeSlot[] = [];
      const dateKey = date.toISOString().split("T")[0];
      const dayOfWeek = date.getDay();
      const now = new Date();

      // Get working hours for this day
      const dayNames = [
        "sunday",
        "monday",
        "tuesday",
        "wednesday",
        "thursday",
        "friday",
        "saturday",
      ] as const;
      const dayName = dayNames[dayOfWeek];
      const daySchedule = SCHEDULER_CONFIG.businessHours[dayName];

      if (!daySchedule) {
        return slots; // No working hours for this day
      }

      const [startHour, startMinute] = daySchedule.start.split(":").map(Number);
      const [endHour, endMinute] = daySchedule.end.split(":").map(Number);

      // Get taken time slots from database for this date
      const takenTimes = fetchedBookings.get(dateKey) || new Set<string>();

      // Generate slots at configured interval
      let currentHour = startHour;
      let currentMinute = startMinute;

      while (
        currentHour < endHour ||
        (currentHour === endHour && currentMinute < endMinute)
      ) {
        const slotTime = `${String(currentHour).padStart(2, "0")}:${String(currentMinute).padStart(2, "0")}`;
        const slotDateTime = new Date(date);
        slotDateTime.setHours(currentHour, currentMinute, 0, 0);

        // Check if slot would extend beyond business hours
        const slotEndTime = new Date(slotDateTime);
        slotEndTime.setMinutes(slotEndTime.getMinutes() + duration);

        const slotEndHour = slotEndTime.getHours();
        const slotEndMinute = slotEndTime.getMinutes();

        const exceedsBusinessHours =
          slotEndHour > endHour ||
          (slotEndHour === endHour && slotEndMinute > endMinute);

        if (exceedsBusinessHours) {
          // Skip this slot, move to next
          currentMinute += SCHEDULER_CONFIG.slotInterval;
          if (currentMinute >= 60) {
            currentHour += Math.floor(currentMinute / 60);
            currentMinute = currentMinute % 60;
          }
          continue;
        }

        // Check availability
        const slotKey = `${dateKey}_${slotTime}`;
        const isBooked = bookedSlots.has(slotKey) || takenTimes.has(slotTime); // Check both props and database
        const isPast = slotDateTime <= now;
        const meetsAdvance = meetsMinimumAdvance(slotDateTime);

        let available = true;
        let unavailableReason: TimeSlot["unavailableReason"] = undefined;

        if (isPast) {
          available = false;
          unavailableReason = "past";
        } else if (isBooked) {
          available = false;
          unavailableReason = "booked";
        } else if (!meetsAdvance) {
          available = false;
          unavailableReason = "too-soon";
        }

        slots.push({
          time: slotTime,
          dateTime: slotDateTime.toISOString(),
          available,
          unavailableReason,
        });

        // Move to next slot
        currentMinute += SCHEDULER_CONFIG.slotInterval;
        if (currentMinute >= 60) {
          currentHour += Math.floor(currentMinute / 60);
          currentMinute = currentMinute % 60;
        }
      }

      return slots;
    },
    [duration, bookedSlots, fetchedBookings],
  );

  /**
   * Get time slots for a specific date
   */
  const getTimeSlotsForDate = useCallback(
    (date: Date): TimeSlot[] => {
      return generateTimeSlotsForDate(date);
    },
    [generateTimeSlotsForDate],
  );

  /**
   * Fetch bookings for a specific date (for real-time availability check)
   */
  const fetchBookingsForDate = useCallback(async (date: Date) => {
    setLoadingTimeSlots(true);
    try {
      const dateString = date.toISOString().split("T")[0];

      const response = await fetch(`/api/demo/bookings?date=${dateString}`);
      const data = await response.json();

      if (data.success && data.takenSlots) {
        const timesSet = new Set<string>();
        data.takenSlots.forEach((slot: { time: string }) => {
          timesSet.add(slot.time);
        });

        // Update the bookings map with fresh data for this date
        setFetchedBookings((prev) => {
          const newMap = new Map(prev);
          newMap.set(dateString, timesSet);
          return newMap;
        });
      }
    } catch (err) {
      console.error(
        `Failed to fetch bookings for ${date.toISOString().split("T")[0]}:`,
        err,
      );
    } finally {
      setLoadingTimeSlots(false);
    }
  }, []);

  /**
   * Refresh availability data
   */
  const refreshAvailability = useCallback(() => {
    setRefreshKey((prev) => prev + 1);
  }, []);

  // Fetch existing bookings from database (initial load for visible dates)
  // With 6-month window, we only pre-fetch next 30 days to avoid excessive API calls
  // Additional dates are fetched on-demand when user selects them
  useEffect(() => {
    const fetchBookingsForDates = async () => {
      try {
        const today = new Date();
        const datePromises: Promise<void>[] = [];
        const bookingsMap = new Map<string, Set<string>>();

        // Pre-fetch bookings for next 30 days (performance optimization)
        const prefetchDays = Math.min(
          30,
          SCHEDULER_CONFIG.maxAdvanceBookingDays,
        );
        for (let i = 0; i < prefetchDays; i++) {
          const date = new Date(today);
          date.setDate(date.getDate() + i);
          const dateString = date.toISOString().split("T")[0];

          const promise = fetch(`/api/demo/bookings?date=${dateString}`)
            .then((res) => res.json())
            .then((data) => {
              if (data.success && data.takenSlots) {
                const timesSet = new Set<string>();
                data.takenSlots.forEach((slot: { time: string }) => {
                  timesSet.add(slot.time);
                });
                bookingsMap.set(dateString, timesSet);
              }
            })
            .catch((err) =>
              console.error(`Failed to fetch bookings for ${dateString}:`, err),
            );

          datePromises.push(promise);
        }

        await Promise.all(datePromises);
        setFetchedBookings(bookingsMap);
      } catch (err) {
        console.error("Failed to fetch existing bookings:", err);
      }
    };

    fetchBookingsForDates();
  }, [refreshKey]);

  // Generate availability on mount and when duration or refreshKey changes
  // Inline the logic to avoid useCallback dependency chain
  useEffect(() => {
    setLoading(true);
    setError(null);

    try {
      const dates: BookingDate[] = [];
      const today = new Date();
      today.setHours(0, 0, 0, 0);

      // Generate dates for next 6 months (booking window)
      for (let i = 0; i < SCHEDULER_CONFIG.maxAdvanceBookingDays; i++) {
        const date = new Date(today);
        date.setDate(date.getDate() + i);

        // Check if date is within booking window and is a business day
        if (!isWithinBookingWindow(date) || !isBusinessDay(date)) {
          continue;
        }

        // Generate time slots for this date
        const timeSlots = generateTimeSlotsForDate(date);

        // Count available slots
        const availableSlots = timeSlots.filter(
          (slot) => slot.available,
        ).length;

        const dateString = date.toISOString().split("T")[0];
        const dayOfWeek = date.getDay();

        dates.push({
          date,
          dateString,
          dayOfWeek,
          bookable: availableSlots > 0,
          availableSlots,
        });
      }

      setAvailableDates(dates);
    } catch (err) {
      setError(
        err instanceof Error ? err.message : "Failed to load availability",
      );
    } finally {
      setLoading(false);
    }
  }, [duration, refreshKey, generateTimeSlotsForDate, fetchedBookings]); // Safe dependencies - only change when they should

  return {
    availableDates,
    loading,
    loadingTimeSlots,
    error,
    getTimeSlotsForDate,
    fetchBookingsForDate,
    refreshAvailability,
  };
}
