import React from "react";
import { Button } from "./button";
import { DatePicker } from "./date-picker";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from "./dialog";
import { Input } from "./input";
import { Label } from "./label";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "./select";
import { Separator } from "./separator";
import { Switch } from "./switch";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./tabs";
import { cn } from "@/lib/utils";
import { formatDateWithWeekday } from "@/lib/format-date";
import { Plus, X } from "lucide-react";

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

export interface AppointmentDaySchedule {
  day: "Mon" | "Tue" | "Wed" | "Thu" | "Fri" | "Sat" | "Sun";
  enabled: boolean;
  /** 24h format — "09:00" */
  startTime: string;
  /** 24h format — "17:00" */
  endTime: string;
}

export interface AppointmentAvailabilityPrefs {
  meetingDuration: string;
  schedulingBuffer: string;
  maxSlotsPerDay: string;
  /** IANA timezone identifier — e.g. "Australia/Sydney" */
  timezone: string;
  /**
   * Online meeting platform brokers require clients to use.
   * "any" means the client can choose between Google Meet and Microsoft Teams.
   */
  defaultMeetingPlatform?: "google-meet" | "microsoft-teams" | "any";
}

export interface AppointmentBlockedDate {
  /** ISO date string — "2026-04-25" */
  date: string;
  /** Human-readable label — "ANZAC Day" */
  label?: string;
  /** Partial day — start time in 24h format "09:00" */
  timeStart?: string;
  /** Partial day — end time in 24h format "17:00" */
  timeEnd?: string;
}

export interface AppointmentAvailabilitySettingsProps {
  /**
   * Initial weekly schedule.
   * @remarks Mount-time initialiser only — prop changes after mount are ignored.
   * Use the `key` prop to reset the component when async data arrives.
   */
  schedule: AppointmentDaySchedule[];
  /**
   * Saved booking preferences from DB — initialises the Booking Preferences tab.
   * @remarks Mount-time initialiser only.
   */
  prefs?: AppointmentAvailabilityPrefs;
  /**
   * Custom blocked dates added by the user (non-holiday).
   * Merged with `publicHolidays` in the Time Off tab.
   * @remarks Mount-time initialiser only.
   */
  blockedDates?: AppointmentBlockedDate[];
  /**
   * Public holiday list from the DB/API.
   * When provided, replaces the built-in `AU_PUBLIC_HOLIDAYS_2026` fallback.
   * @remarks Mount-time initialiser only.
   */
  publicHolidays?: AppointmentBlockedDate[];
  onSave?: (
    schedule: AppointmentDaySchedule[],
    prefs: AppointmentAvailabilityPrefs
  ) => void;
  onBlockedDatesChange?: (dates: AppointmentBlockedDate[]) => void;
  /** Fired immediately whenever any booking preference value changes (before Save). */
  onPrefsChange?: (prefs: AppointmentAvailabilityPrefs) => void;
}

// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------

/** Fallback holiday list used when the `publicHolidays` prop is not provided. */
const AU_PUBLIC_HOLIDAYS_2026: AppointmentBlockedDate[] = [
  { date: "2026-01-01", label: "New Year's Day" },
  { date: "2026-01-26", label: "Australia Day" },
  { date: "2026-04-03", label: "Good Friday" },
  { date: "2026-04-04", label: "Easter Saturday" },
  { date: "2026-04-06", label: "Easter Monday" },
  { date: "2026-04-25", label: "ANZAC Day" },
  { date: "2026-06-08", label: "King's Birthday" },
  { date: "2026-12-25", label: "Christmas Day" },
  { date: "2026-12-26", label: "Boxing Day" },
];

export const TIMEZONE_OPTIONS: { value: string; label: string }[] = [
  { value: "Australia/Sydney", label: "Sydney / Melbourne (AEDT)" },
  { value: "Australia/Brisbane", label: "Brisbane (AEST, no DST)" },
  { value: "Australia/Adelaide", label: "Adelaide (ACDT)" },
  { value: "Australia/Perth", label: "Perth (AWST)" },
  { value: "Australia/Darwin", label: "Darwin (ACST, no DST)" },
  { value: "Australia/Hobart", label: "Hobart (AEDT)" },
  { value: "Asia/Ho_Chi_Minh", label: "Ho Chi Minh City (ICT)" },
  { value: "Asia/Singapore", label: "Singapore (SGT)" },
  { value: "UTC", label: "UTC" },
];

export const MEETING_PLATFORM_OPTIONS: {
  value: "google-meet" | "microsoft-teams" | "any";
  label: string;
}[] = [
  { value: "google-meet", label: "Google Meet" },
  { value: "microsoft-teams", label: "Microsoft Teams" },
  { value: "any", label: "Let client choose" },
];

const MEETING_DURATION_OPTIONS: { value: string; label: string }[] = [
  { value: "15", label: "15 minutes" },
  { value: "30", label: "30 minutes" },
  { value: "45", label: "45 minutes" },
  { value: "60", label: "60 minutes" },
  { value: "90", label: "90 minutes" },
];

const SCHEDULING_BUFFER_OPTIONS: { value: string; label: string }[] = [
  { value: "0", label: "No buffer" },
  { value: "5", label: "5 minutes" },
  { value: "10", label: "10 minutes" },
  { value: "15", label: "15 minutes" },
  { value: "30", label: "30 minutes" },
];

const MAX_SLOTS_OPTIONS: { value: string; label: string }[] = [
  { value: "2", label: "2 per day" },
  { value: "4", label: "4 per day" },
  { value: "6", label: "6 per day" },
  { value: "8", label: "8 per day" },
  { value: "10", label: "10 per day" },
  { value: "unlimited", label: "Unlimited" },
];

/** Map a Base UI SelectValue `v` to its display label from an options array. */
const selectLabel = (opts: { value: string; label: string }[], v: unknown) =>
  opts.find((o) => o.value === String(v))?.label ?? String(v ?? "");

// 30-min increments from 06:00 to 21:30
const TIME_OPTIONS: { value: string; label: string }[] = (() => {
  const opts: { value: string; label: string }[] = [];
  for (let h = 6; h <= 21; h++) {
    for (const m of [0, 30]) {
      const hh = String(h).padStart(2, "0");
      const mm = String(m).padStart(2, "0");
      const hour12 = h === 0 ? 12 : h > 12 ? h - 12 : h;
      const ampm = h < 12 ? "AM" : "PM";
      opts.push({ value: `${hh}:${mm}`, label: `${hour12}:${mm} ${ampm}` });
    }
  }
  return opts;
})();

const timeLabel = (v: string) =>
  TIME_OPTIONS.find((o) => o.value === v)?.label ?? v;

// ---------------------------------------------------------------------------
// Internal type
// ---------------------------------------------------------------------------

interface TimeOffEntry {
  date: string;
  label?: string;
  enabled: boolean;
  isHoliday: boolean;
  timeStart?: string;
  timeEnd?: string;
}

// ---------------------------------------------------------------------------
// Sub-components
// ---------------------------------------------------------------------------

function TimeSelect({
  value,
  onChange,
  disabled,
}: {
  value: string;
  onChange: (v: string) => void;
  disabled?: boolean;
}) {
  return (
    <Select
      value={value}
      onValueChange={(v) => onChange(v as string)}
      disabled={disabled}
    >
      <SelectTrigger className="w-36">
        <SelectValue>{(v) => selectLabel(TIME_OPTIONS, v)}</SelectValue>
      </SelectTrigger>
      <SelectContent>
        {TIME_OPTIONS.map((opt) => (
          <SelectItem key={opt.value} value={opt.value}>
            {opt.label}
          </SelectItem>
        ))}
      </SelectContent>
    </Select>
  );
}

/** Shared row layout for the Booking Preferences tab. */
function PrefRow({
  label,
  description,
  children,
}: {
  label: string;
  description: string;
  children: React.ReactNode;
}) {
  return (
    <div className="flex min-h-[68px] items-center justify-between px-6 py-4">
      <div className="flex flex-col gap-0.5">
        <p className="text-sm font-medium">{label}</p>
        <p className="text-xs text-muted-foreground">{description}</p>
      </div>
      {children}
    </div>
  );
}

function AddTimeOffDialog({
  open,
  onOpenChange,
  onAdd,
}: {
  open: boolean;
  onOpenChange: (v: boolean) => void;
  onAdd: (entry: AppointmentBlockedDate) => void;
}) {
  const [label, setLabel] = React.useState("");
  const [date, setDate] = React.useState<Date | undefined>(undefined);
  const [includeTime, setIncludeTime] = React.useState(false);
  const [timeStart, setTimeStart] = React.useState("09:00");
  const [timeEnd, setTimeEnd] = React.useState("17:00");

  const reset = () => {
    setLabel("");
    setDate(undefined);
    setIncludeTime(false);
    setTimeStart("09:00");
    setTimeEnd("17:00");
  };

  const handleAdd = () => {
    if (!date) return;
    const isoDate = [
      date.getFullYear(),
      String(date.getMonth() + 1).padStart(2, "0"),
      String(date.getDate()).padStart(2, "0"),
    ].join("-");
    onAdd({
      date: isoDate,
      label: label.trim() || undefined,
      timeStart: includeTime ? timeStart : undefined,
      timeEnd: includeTime ? timeEnd : undefined,
    });
    reset();
    onOpenChange(false);
  };

  const handleCancel = () => {
    reset();
    onOpenChange(false);
  };

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent size="auto" minWidth="26rem">
        <DialogHeader>
          <DialogTitle>Add time off</DialogTitle>
          <DialogDescription>
            Block a date when you are unavailable. Clients cannot book on this
            date.
          </DialogDescription>
        </DialogHeader>

        <div className="flex flex-col gap-4 py-1">
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="toff-label">
              Name{" "}
              <span className="font-normal text-muted-foreground">
                (optional)
              </span>
            </Label>
            <Input
              id="toff-label"
              placeholder="e.g. Conference, Personal day…"
              value={label}
              onChange={(e) => setLabel(e.target.value)}
            />
          </div>

          <div className="flex flex-col gap-1.5">
            <Label>Date</Label>
            <DatePicker
              value={date}
              onChange={setDate}
              placeholder="Pick a date"
              calendarProps={{ fromDate: new Date() }}
            />
          </div>

          <div className="flex flex-col gap-3">
            <div className="flex items-center gap-3">
              <Switch
                id="toff-include-time"
                checked={includeTime}
                onCheckedChange={setIncludeTime}
              />
              <Label
                htmlFor="toff-include-time"
                className="cursor-pointer text-sm"
              >
                Specify hours (partial day)
              </Label>
            </div>

            {includeTime && (
              <div className="flex items-center gap-3 pl-10">
                <TimeSelect value={timeStart} onChange={setTimeStart} />
                <span className="text-sm text-muted-foreground">to</span>
                <TimeSelect value={timeEnd} onChange={setTimeEnd} />
              </div>
            )}
          </div>
        </div>

        <DialogFooter>
          <Button variant="outline" onClick={handleCancel}>
            Cancel
          </Button>
          <Button onClick={handleAdd} disabled={!date}>
            Add
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

// ---------------------------------------------------------------------------
// Component
// ---------------------------------------------------------------------------

export function AppointmentAvailabilitySettings({
  schedule: initialSchedule,
  prefs: prefsProp,
  blockedDates: blockedDatesProp,
  publicHolidays: publicHolidaysProp,
  onSave,
  onBlockedDatesChange,
  onPrefsChange,
}: AppointmentAvailabilitySettingsProps) {
  const [schedule, setSchedule] =
    React.useState<AppointmentDaySchedule[]>(initialSchedule);

  const [meetingDuration, setMeetingDuration] = React.useState(
    prefsProp?.meetingDuration ?? "30"
  );
  const [schedulingBuffer, setSchedulingBuffer] = React.useState(
    prefsProp?.schedulingBuffer ?? "0"
  );
  const [maxSlotsPerDay, setMaxSlotsPerDay] = React.useState(
    prefsProp?.maxSlotsPerDay ?? "8"
  );
  const [timezone, setTimezone] = React.useState(
    prefsProp?.timezone ?? "Australia/Sydney"
  );
  const [defaultMeetingPlatform, setDefaultMeetingPlatform] = React.useState<
    "google-meet" | "microsoft-teams" | "any"
  >(prefsProp?.defaultMeetingPlatform ?? "any");

  const [timeOffEntries, setTimeOffEntries] = React.useState<TimeOffEntry[]>(
    () => {
      const holidays = publicHolidaysProp ?? AU_PUBLIC_HOLIDAYS_2026;
      const holidayDates = new Set(holidays.map((h) => h.date));
      const entries: TimeOffEntry[] = holidays.map((h) => ({
        ...h,
        enabled: true,
        isHoliday: true,
      }));
      blockedDatesProp?.forEach((b) => {
        if (!holidayDates.has(b.date)) {
          entries.push({
            date: b.date,
            label: b.label,
            enabled: true,
            isHoliday: false,
          });
        }
      });
      return entries.sort((a, b) => a.date.localeCompare(b.date));
    }
  );
  const [addMoreOpen, setAddMoreOpen] = React.useState(false);

  // ---------------------------------------------------------------------------
  // Auto-save effects
  //
  // Each effect uses its own mount-guard ref. A shared ref would break because
  // React runs effects in declaration order within the same flush — the first
  // effect would flip the ref to `true`, causing later effects to skip their
  // own guard and fire onSave/onBlockedDatesChange on mount.
  // ---------------------------------------------------------------------------

  const currentPrefs = React.useMemo(
    () => ({
      meetingDuration,
      schedulingBuffer,
      maxSlotsPerDay,
      timezone,
      defaultMeetingPlatform,
    }),
    [
      meetingDuration,
      schedulingBuffer,
      maxSlotsPerDay,
      timezone,
      defaultMeetingPlatform,
    ]
  );

  const saveGuard = React.useRef(false);
  const timeOffGuard = React.useRef(false);
  const prefsChangeGuard = React.useRef(false);

  React.useEffect(() => {
    if (!saveGuard.current) {
      saveGuard.current = true;
      return;
    }
    onSave?.(schedule, currentPrefs);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [schedule, currentPrefs]);

  React.useEffect(() => {
    if (!prefsChangeGuard.current) {
      prefsChangeGuard.current = true;
      return;
    }
    onPrefsChange?.(currentPrefs);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [currentPrefs]);

  React.useEffect(() => {
    if (!timeOffGuard.current) {
      timeOffGuard.current = true;
      return;
    }
    const blocked = timeOffEntries
      .filter((e) => e.enabled)
      .map(({ date, label, timeStart, timeEnd }) => ({
        date,
        label,
        timeStart,
        timeEnd,
      }));
    onBlockedDatesChange?.(blocked);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [timeOffEntries]);

  // ---------------------------------------------------------------------------
  // Weekly schedule handlers
  // ---------------------------------------------------------------------------

  const toggleDay = (index: number) => {
    setSchedule((prev) =>
      prev.map((d, i) => (i === index ? { ...d, enabled: !d.enabled } : d))
    );
  };

  const updateTime = (
    index: number,
    field: "startTime" | "endTime",
    value: string
  ) => {
    setSchedule((prev) =>
      prev.map((d, i) => (i === index ? { ...d, [field]: value } : d))
    );
  };

  // ---------------------------------------------------------------------------
  // Time Off handlers
  // ---------------------------------------------------------------------------

  const toggleTimeOff = (date: string, enabled: boolean) => {
    setTimeOffEntries((prev) =>
      prev.map((e) => (e.date === date ? { ...e, enabled } : e))
    );
  };

  const removeCustomDate = (date: string) => {
    setTimeOffEntries((prev) => prev.filter((e) => e.date !== date));
  };

  const handleAddMore = (entry: AppointmentBlockedDate) => {
    if (timeOffEntries.some((e) => e.date === entry.date)) return;
    setTimeOffEntries((prev) =>
      [
        ...prev,
        {
          date: entry.date,
          label: entry.label,
          enabled: true,
          isHoliday: false,
          timeStart: entry.timeStart,
          timeEnd: entry.timeEnd,
        },
      ].sort((a, b) => a.date.localeCompare(b.date))
    );
  };

  return (
    <div className="border border-border bg-card">
      <Tabs defaultValue="weekly" className="gap-0">
        <div className="border-b border-border p-1">
          <TabsList variant="default" className="w-full">
            <TabsTrigger value="weekly">Weekly Availability</TabsTrigger>
            <TabsTrigger value="booking">Booking Preferences</TabsTrigger>
            <TabsTrigger value="time-off">Time Off</TabsTrigger>
          </TabsList>
        </div>

        {/* Tab 1: Weekly Availability */}
        <TabsContent value="weekly">
          <div className="flex flex-col divide-y divide-border">
            {schedule.map((day, index) => (
              <div
                key={day.day}
                className="flex min-h-[68px] items-center gap-5 px-6 py-4"
              >
                <div className="flex w-28 items-center gap-3">
                  <Switch
                    id={`day-${day.day}`}
                    checked={day.enabled}
                    onCheckedChange={() => toggleDay(index)}
                  />
                  <Label
                    htmlFor={`day-${day.day}`}
                    className="cursor-pointer text-sm font-medium"
                  >
                    {day.day}
                  </Label>
                </div>

                {day.enabled ? (
                  <div className="flex flex-1 items-center gap-3">
                    <TimeSelect
                      value={day.startTime}
                      onChange={(v) => updateTime(index, "startTime", v)}
                    />
                    <span className="text-sm text-muted-foreground">to</span>
                    <TimeSelect
                      value={day.endTime}
                      onChange={(v) => updateTime(index, "endTime", v)}
                    />
                  </div>
                ) : (
                  <p className="flex-1 text-sm text-muted-foreground">
                    Unavailable
                  </p>
                )}
              </div>
            ))}
          </div>
        </TabsContent>

        {/* Tab 2: Booking Preferences */}
        <TabsContent value="booking">
          <div className="flex flex-col divide-y divide-border">
            <PrefRow
              label="Meeting Duration"
              description="Length of each appointment slot"
            >
              <Select
                value={meetingDuration}
                onValueChange={(v) => setMeetingDuration(v as string)}
              >
                <SelectTrigger className="w-64">
                  <SelectValue>
                    {(v) => selectLabel(MEETING_DURATION_OPTIONS, v)}
                  </SelectValue>
                </SelectTrigger>
                <SelectContent>
                  {MEETING_DURATION_OPTIONS.map((opt) => (
                    <SelectItem key={opt.value} value={opt.value}>
                      {opt.label}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </PrefRow>

            <PrefRow
              label="Scheduling Buffer"
              description="Gap between consecutive bookings"
            >
              <Select
                value={schedulingBuffer}
                onValueChange={(v) => setSchedulingBuffer(v as string)}
              >
                <SelectTrigger className="w-64">
                  <SelectValue>
                    {(v) => selectLabel(SCHEDULING_BUFFER_OPTIONS, v)}
                  </SelectValue>
                </SelectTrigger>
                <SelectContent>
                  {SCHEDULING_BUFFER_OPTIONS.map((opt) => (
                    <SelectItem key={opt.value} value={opt.value}>
                      {opt.label}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </PrefRow>

            <PrefRow
              label="Daily Slot Limit"
              description="Maximum bookings accepted per day"
            >
              <Select
                value={maxSlotsPerDay}
                onValueChange={(v) => setMaxSlotsPerDay(v as string)}
              >
                <SelectTrigger className="w-64">
                  <SelectValue>
                    {(v) => selectLabel(MAX_SLOTS_OPTIONS, v)}
                  </SelectValue>
                </SelectTrigger>
                <SelectContent>
                  {MAX_SLOTS_OPTIONS.map((opt) => (
                    <SelectItem key={opt.value} value={opt.value}>
                      {opt.label}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </PrefRow>

            <PrefRow
              label="Meeting Timezone"
              description="Timezone used for booking display and notifications"
            >
              <Select
                value={timezone}
                onValueChange={(v) => setTimezone(v as string)}
              >
                <SelectTrigger className="w-64">
                  <SelectValue>
                    {(v) => selectLabel(TIMEZONE_OPTIONS, v)}
                  </SelectValue>
                </SelectTrigger>
                <SelectContent>
                  {TIMEZONE_OPTIONS.map((opt) => (
                    <SelectItem key={opt.value} value={opt.value}>
                      {opt.label}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </PrefRow>

            <PrefRow
              label="Default Meeting Platform"
              description="Online meeting platform clients must use when booking"
            >
              <Select
                value={defaultMeetingPlatform}
                onValueChange={(v) =>
                  setDefaultMeetingPlatform(
                    v as "google-meet" | "microsoft-teams" | "any"
                  )
                }
              >
                <SelectTrigger className="w-64">
                  <SelectValue>
                    {(v) => selectLabel(MEETING_PLATFORM_OPTIONS, v)}
                  </SelectValue>
                </SelectTrigger>
                <SelectContent>
                  {MEETING_PLATFORM_OPTIONS.map((opt) => (
                    <SelectItem key={opt.value} value={opt.value}>
                      {opt.label}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </PrefRow>
          </div>
        </TabsContent>

        {/* Tab 3: Time Off */}
        <TabsContent value="time-off">
          <div className="flex items-center justify-between px-6 py-4">
            <p className="text-sm text-muted-foreground">
              Toggle dates when you are unavailable. Clients cannot book on
              switched-on dates.
            </p>
            <Button
              variant="outline"
              size="sm"
              className="ml-4 shrink-0 gap-1.5"
              onClick={() => setAddMoreOpen(true)}
            >
              <Plus className="h-3.5 w-3.5" />
              Add more
            </Button>
          </div>

          <Separator />

          <div className="flex flex-col divide-y divide-border">
            {timeOffEntries.map((entry) => {
              const formattedDate = formatDateWithWeekday(entry.date);
              const hasTimeRange = entry.timeStart && entry.timeEnd;
              return (
                <div
                  key={entry.date}
                  className="flex min-h-[68px] items-center gap-5 px-6 py-4"
                >
                  <Switch
                    id={`toff-${entry.date}`}
                    checked={entry.enabled}
                    onCheckedChange={(v) => toggleTimeOff(entry.date, v)}
                  />
                  <div className="min-w-0 flex-1">
                    <Label
                      htmlFor={`toff-${entry.date}`}
                      className={cn(
                        "cursor-pointer text-sm font-medium",
                        !entry.enabled && "text-muted-foreground"
                      )}
                    >
                      {entry.label ?? formattedDate}
                    </Label>
                    <p className="text-xs text-muted-foreground">
                      {entry.label ? formattedDate : null}
                      {hasTimeRange && (
                        <>
                          {entry.label ? " · " : ""}
                          {timeLabel(entry.timeStart!)} –{" "}
                          {timeLabel(entry.timeEnd!)}
                        </>
                      )}
                    </p>
                  </div>
                  {!entry.isHoliday && (
                    <Button
                      type="button"
                      variant="ghost"
                      size="icon"
                      className="h-7 w-7 text-muted-foreground hover:text-destructive"
                      onClick={() => removeCustomDate(entry.date)}
                    >
                      <X className="h-3.5 w-3.5" />
                    </Button>
                  )}
                </div>
              );
            })}
          </div>
        </TabsContent>
      </Tabs>

      <AddTimeOffDialog
        open={addMoreOpen}
        onOpenChange={setAddMoreOpen}
        onAdd={handleAddMore}
      />
    </div>
  );
}
