/**
 * CalendarShell — Schedule-X calendar wired to TanStack Query + hydrateEvents + Zustand range.
 *
 * Data flow:
 *   Zustand calendarRange → TanStack Query ['events', start, end] → fetchEvents
 *   → hydrateEvents (ISO → Temporal) → eventsService.set() → Schedule-X render
 *
 * Critical constraints:
 *   - Plugins (eventsService) created once via useState stable initialiser
 *   - onRangeUpdate fires on navigation; initial fetch uses Zustand default range (A4/Q2)
 *   - DateRange.start/end are Temporal.ZonedDateTime → converted to 'YYYY-MM-DD' for Zustand
 *   - calendarId routing: 'shared' | String(ownerUserId) — produced by hydrateEvents, consumed
 *     by buildCalendarConfig; never the DB calendar-row id
 *   - Event popover: driven exclusively by Zustand openEventId via onEventClick → standalone
 *     EventDetailPopover; createEventModalPlugin and customComponents.eventModal are NOT used
 *   - Threat T-02d-01: all event fields are plain-text JSX children — no raw HTML injection
 *   - Navigation: Schedule-X's built-in header is the sole navigation bar (Today/‹›/view switcher
 *     + weekday-name row). The custom ViewToolbar and calendar-controls plugin have been removed.
 *
 * Layout:
 *   - Phone (≤767px): AppNav top bar → Schedule-X (header + grid)
 *   - Tablet/Desktop (≥768px): AppNav left sidebar (240px) + main area (Schedule-X header + grid)
 *
 * State branches:
 *   isLoading (initial) → SkeletonCalendar
 *   success + 0 occurrences → EmptyState
 *   isError (after retry:2) → error state with Retry button
 *   success + events → ScheduleXCalendar
 */

import { useState, useEffect, useMemo } from 'react'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { ScheduleXCalendar, useCalendarApp } from '@schedule-x/react'
import {
  createViewDay,
  createViewWeek,
  createViewMonthGrid,
  createViewMonthAgenda,
  type CalendarType,
} from '@schedule-x/calendar'
import { createEventsServicePlugin } from '@schedule-x/events-service'
import { Plus } from 'lucide-react'

import { fetchMe, fetchEvents } from '../api/client.js'
import { hydrateEvents } from '../lib/hydrateEvents.js'
import { maybeRedirectToLogin, clearLoginRedirect } from '../lib/loginRedirect.js'
import { buildCalendarConfig, SX_FIRST_DAY_OF_WEEK } from '../lib/calendarConfig.js'
import { useCalendarStore } from '../store/calendarStore.js'
import { EventDetailPopover } from './EventDetailPopover.js'
import { EventForm } from './EventForm.js'
import { DeleteConfirmationDialog } from './DeleteConfirmationDialog.js'
import { SyncStateToast } from './SyncStateToast.js'
import { AppNav } from './AppNav.js'
import { ColorLegend } from './ColorLegend.js'
import { SkeletonCalendar } from './SkeletonCalendar.js'
import { InstallPrompt } from './InstallPrompt.js'

// ── Helpers ────────────────────────────────────────────────────────────────

/**
 * D-05: phone (≤767px) defaults to 'month-agenda'; tablet/desktop to 'month-grid'.
 * Called once at render time; Schedule-X persists selected view internally after that.
 */
function resolveDefaultView(persistedView: string): string {
  if (typeof window === 'undefined') return 'month-grid'
  return persistedView
}

function isPhone(): boolean {
  return typeof window !== 'undefined' && window.matchMedia('(max-width: 767px)').matches
}

// ── Component ──────────────────────────────────────────────────────────────

export function CalendarShell() {
  // Use per-field selectors so CalendarShell does NOT subscribe to openEventId.
  // Without selectors, any popover open/close triggers a full re-render here,
  // which rebuilds the Schedule-X config and causes a visible calendar flash (Bug B).
  const calendarRange = useCalendarStore((s) => s.calendarRange)
  const setCalendarRange = useCalendarStore((s) => s.setCalendarRange)
  const setOpenEventId = useCalendarStore((s) => s.setOpenEventId)
  const selectedView = useCalendarStore((s) => s.selectedView)
  const setEventForm = useCalendarStore((s) => s.setEventForm)
  const eventFormOpen = useCalendarStore((s) => s.eventFormOpen)
  const { start, end } = calendarRange
  const queryClient = useQueryClient()

  // Fetch current user to build per-member color config
  const meQuery = useQuery({
    queryKey: ['me'],
    queryFn: fetchMe,
    retry: false,
    staleTime: 5 * 60 * 1000,
  })

  // Fetch windowed occurrences — key includes start/end so navigation refetches
  const eventsQuery = useQuery({
    queryKey: ['events', start, end],
    queryFn: () => fetchEvents(start, end),
    retry: 2,
    staleTime: 5 * 60 * 1000,
  })

  // Create plugins once (stable across renders)
  const eventsService = useState(() => createEventsServicePlugin())[0]

  // Build members list from /api/me for AppNav + ColorLegend
  const members = useMemo(() => {
    if (!meQuery.data?.user) return []
    return [
      {
        id: String(meQuery.data.user.id),
        name: meQuery.data.user.displayName ?? 'Member',
        color: meQuery.data.user.color,
      },
    ]
  }, [meQuery.data])

  // Build calendars config whenever the authenticated user changes
  const calendarsConfig: Record<string, CalendarType> = useMemo(
    () => buildCalendarConfig(members).calendars,
    [members],
  )

  const defaultView = resolveDefaultView(selectedView)

  // Display events in the VIEWER's local timezone. Schedule-X defaults to 'UTC', which made
  // a 17:45-04:00 event render at 21:45 (9:45 PM). Events arrive as zoned ISO strings in their
  // own IANA zones (Toronto/Detroit/New_York/Edmonton); Schedule-X converts them to this one
  // display zone, so the family sees every event in their own wall-clock time.
  // IANATimezone (the config's timezone type) is declared but not exported by @schedule-x/calendar,
  // so derive it from useCalendarApp's config parameter rather than importing it.
  type SxTimeZone = NonNullable<Parameters<typeof useCalendarApp>[0]['timezone']>
  const displayTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone as SxTimeZone

  // useCalendarApp — config is stable; plugins passed as second argument
  const calendar = useCalendarApp(
    {
      views: [
        createViewDay(),
        createViewWeek(),
        createViewMonthGrid(),
        createViewMonthAgenda(),
      ],
      defaultView,
      timezone: displayTimeZone,
      firstDayOfWeek: SX_FIRST_DAY_OF_WEEK as 7,
      calendars: calendarsConfig,
      callbacks: {
        onRangeUpdate(range) {
          // range.start / range.end are Temporal.ZonedDateTime.
          // Set end to the day AFTER range.end (exclusive window end) so that:
          //   - Day view: start === day N, end === day N+1 → 1-day window (avoids 400 on 0-day span)
          //   - Week/Month: end includes the last visible day instead of dropping it
          setCalendarRange({
            start: range.start.toPlainDate().toString(),
            end: range.end.toPlainDate().add({ days: 1 }).toString(),
          })
        },
        onEventClick(event) {
          if (event.id != null) {
            setOpenEventId(String(event.id))
          }
        },
      },
    },
    [eventsService],
  )

  // Sync TanStack Query result into Schedule-X eventsService (Pitfall 4 guard)
  // hydrateEvents converts ISO strings → Temporal before eventsService.set()
  useEffect(() => {
    if (!eventsQuery.data) return
    const sxEvents = hydrateEvents(eventsQuery.data.occurrences)
    eventsService.set(sxEvents as Parameters<typeof eventsService.set>[0])
  }, [eventsQuery.data, eventsService])

  // Auth redirect — one-shot full-page nav to /api/login when /api/me fails.
  // If this is the first failure, maybeRedirectToLogin() sets a sessionStorage
  // flag and navigates the browser to /api/login (top-level nav, no CORS block).
  // The page will unmount as the browser navigates. If the flag is already set
  // (already bounced through login once and still failing), returns false and the
  // existing "Sign-in required" branch below renders.
  useEffect(() => {
    if (meQuery.isError) {
      maybeRedirectToLogin()
    }
  }, [meQuery.isError])

  // Clear the one-shot flag on a successful /api/me load so a later session
  // expiry can trigger another redirect instead of showing "Sign-in required".
  useEffect(() => {
    if (meQuery.isSuccess) {
      clearLoginRedirect()
    }
  }, [meQuery.isSuccess])

  // ── Render helpers ────────────────────────────────────────────────────────

  // Determine which content to show in the calendar area
  const isInitialLoading = meQuery.isLoading || (eventsQuery.isLoading && !eventsQuery.data)
  const isEventsError = eventsQuery.isError

  const phone = isPhone()

  // ── Sign-in required ───────────────────────────────────────────────────────

  if (meQuery.isError) {
    return (
      <div
        role="alert"
        style={{
          color: 'var(--color-destructive)',
          padding: 'var(--space-4)',
          background: 'var(--color-surface-dim)',
          borderRadius: 'var(--space-2)',
          fontFamily: 'var(--font-family-base)',
        }}
      >
        Sign-in required
      </div>
    )
  }

  // ── Calendar content ───────────────────────────────────────────────────────

  // The content panel (right of sidebar on desktop, full-width on phone)
  function CalendarContent() {
    return (
      <div
        style={{
          flex: 1,
          minWidth: 0,
          display: 'flex',
          flexDirection: 'column',
          overflow: 'hidden',
        }}
      >
        {/* Main calendar area */}
        <div style={{ flex: 1, minHeight: 0, height: '100%', position: 'relative' }}>
          {isInitialLoading ? (
            // Loading state: shimmer skeleton
            <div style={{ padding: 'var(--space-4)', flex: 1 }}>
              <SkeletonCalendar variant={phone ? 'agenda' : 'month'} />
            </div>
          ) : isEventsError ? (
            // Error state: replace grid with heading + body + Retry button
            <div
              style={{
                display: 'flex',
                flexDirection: 'column',
                alignItems: 'center',
                justifyContent: 'center',
                padding: 'var(--space-12)',
                gap: 'var(--space-4)',
                fontFamily: 'var(--font-family-base)',
                textAlign: 'center',
                flex: 1,
              }}
            >
              <h2
                style={{
                  margin: 0,
                  fontSize: 'var(--text-heading-size)',
                  fontWeight: 'var(--text-heading-weight)',
                  lineHeight: 'var(--text-heading-line-height)',
                  color: 'var(--color-text-primary)',
                }}
              >
                Couldn't load events
              </h2>
              <p
                style={{
                  margin: 0,
                  fontSize: 'var(--text-body-size)',
                  color: 'var(--color-text-secondary)',
                }}
              >
                Check your connection and try again.
              </p>
              <button
                onClick={() => queryClient.refetchQueries({ queryKey: ['events'] })}
                style={{
                  background: 'var(--color-surface-dim)',
                  border: '1px solid var(--color-border)',
                  borderRadius: 'var(--space-1)',
                  cursor: 'pointer',
                  minHeight: '44px',
                  padding: '0 var(--space-4)',
                  fontSize: 'var(--text-label-size)',
                  fontWeight: 600,
                  color: 'var(--color-text-primary)',
                  fontFamily: 'var(--font-family-base)',
                }}
              >
                Retry
              </button>
            </div>
          ) : (
            // Normal: Schedule-X calendar (primary focal point).
            // ALWAYS render the calendar even when the window has no events — its built-in
            // header carries the navigation, so swapping in an empty-state would strand the
            // user with no way to navigate away from an empty day/week. An empty grid is clear.
            <ScheduleXCalendar calendarApp={calendar} />
          )}
        </div>

        {/* Phone: show ColorLegend below toolbar, collapsed */}
        {phone && !meQuery.isLoading && members.length > 0 && (
          <div
            style={{
              padding: 'var(--space-2) var(--space-4)',
              borderTop: '1px solid var(--color-border)',
              background: 'var(--color-surface)',
            }}
          >
            <ColorLegend members={members} />
          </div>
        )}
      </div>
    )
  }

  // ── Full layout ────────────────────────────────────────────────────────────

  if (phone) {
    // Phone: stacked layout — AppNav top bar + content below + FAB
    return (
      <div
        style={{
          height: '100dvh',
          display: 'flex',
          flexDirection: 'column',
          background: 'var(--color-surface)',
          color: 'var(--color-text-primary)',
          fontFamily: 'var(--font-family-base)',
          overflow: 'hidden',
        }}
      >
        <AppNav
          members={members}
          currentUserColor={meQuery.data?.user.color}
          currentUserName={meQuery.data?.user.displayName ?? undefined}
        />
        <InstallPrompt />
        <CalendarContent />
        <EventDetailPopover />

        {/* New Event FAB — phone: bottom-right floating action button (UI-SPEC §Interaction Contract) */}
        <button
          aria-label="New Event"
          onClick={() => setEventForm(true, 'create')}
          style={{
            position: 'fixed',
            bottom: 'var(--space-6)',
            right: 'var(--space-6)',
            width: '56px',
            height: '56px',
            minWidth: '56px',
            minHeight: '56px',
            borderRadius: '50%',
            background: 'var(--color-text-primary)',
            color: '#ffffff',
            border: 'none',
            cursor: 'pointer',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            boxShadow: '0 4px 16px rgba(0,0,0,0.18)',
            zIndex: 100,
            fontFamily: 'var(--font-family-base)',
          }}
        >
          <Plus size={24} aria-hidden="true" />
        </button>

        {/* EventForm modal — conditionally rendered while eventFormOpen */}
        {eventFormOpen && <EventForm />}

        {/* DeleteConfirmationDialog — always mounted; renders nothing when deleteDialogOpen is false */}
        <DeleteConfirmationDialog />

        {/* SyncStateToast — always mounted; renders nothing when lastSyncedUid is null */}
        <SyncStateToast />
      </div>
    )
  }

  // Tablet/Desktop: sidebar + main area
  return (
    <div
      style={{
        height: '100dvh',
        display: 'flex',
        flexDirection: 'row',
        background: 'var(--color-surface)',
        color: 'var(--color-text-primary)',
        fontFamily: 'var(--font-family-base)',
        overflow: 'hidden',
      }}
    >
      {/* Left sidebar: AppNav (includes ColorLegend on desktop) */}
      <AppNav
        members={members}
        currentUserColor={meQuery.data?.user.color}
        currentUserName={meQuery.data?.user.displayName ?? undefined}
      />

      {/* Main content area */}
      <div style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
        <InstallPrompt />

        {/* New Event toolbar button — tablet/desktop (UI-SPEC §Interaction Contract) */}
        <div
          style={{
            display: 'flex',
            justifyContent: 'flex-end',
            padding: 'var(--space-2) var(--space-4)',
            borderBottom: '1px solid var(--color-border-subtle)',
            background: 'var(--color-surface)',
            flexShrink: 0,
          }}
        >
          <button
            onClick={() => setEventForm(true, 'create')}
            style={{
              background: 'var(--color-text-primary)',
              color: '#ffffff',
              border: 'none',
              cursor: 'pointer',
              minHeight: '44px',
              padding: '0 var(--space-4)',
              borderRadius: 'var(--space-1)',
              fontSize: 'var(--text-label-size)',
              fontWeight: 600,
              display: 'flex',
              alignItems: 'center',
              gap: 'var(--space-1)',
              fontFamily: 'var(--font-family-base)',
            }}
          >
            <Plus size={16} aria-hidden="true" />
            {/* Plain text — XSS guard */}
            New Event
          </button>
        </div>

        <CalendarContent />
      </div>

      {/* EventDetailPopover — standalone mode driven by Zustand openEventId */}
      <EventDetailPopover />

      {/* EventForm modal — conditionally rendered while eventFormOpen */}
      {eventFormOpen && <EventForm />}

      {/* DeleteConfirmationDialog — always mounted; renders nothing when deleteDialogOpen is false */}
      <DeleteConfirmationDialog />

      {/* SyncStateToast — always mounted; renders nothing when lastSyncedUid is null */}
      <SyncStateToast />
    </div>
  )
}
