<script lang="ts">
import type { CourseResponse } from "$lib/api";
import {
  abbreviateInstructor,
  formatTime,
  formatMeetingDays,
  formatLocation,
  getPrimaryInstructor,
  isMeetingTimeTBA,
  isTimeTBA,
} from "$lib/course";
import CourseDetail from "./CourseDetail.svelte";
import { slide } from "svelte/transition";
import { onMount } from "svelte";
import { OverlayScrollbars } from "overlayscrollbars";
import { themeStore } from "$lib/stores/theme.svelte";
import { createSvelteTable, FlexRender } from "$lib/components/ui/data-table/index.js";
import {
  getCoreRowModel,
  getSortedRowModel,
  type ColumnDef,
  type SortingState,
  type VisibilityState,
  type Updater,
} from "@tanstack/table-core";
import { ArrowUp, ArrowDown, ArrowUpDown, Columns3, Check, RotateCcw } from "@lucide/svelte";
import { DropdownMenu, ContextMenu, Tooltip } from "bits-ui";
import SimpleTooltip from "./SimpleTooltip.svelte";
import { fade, fly } from "svelte/transition";

let {
  courses,
  loading,
  sorting = [],
  onSortingChange,
  manualSorting = false,
  subjectMap = {},
}: {
  courses: CourseResponse[];
  loading: boolean;
  sorting?: SortingState;
  onSortingChange?: (sorting: SortingState) => void;
  manualSorting?: boolean;
  subjectMap?: Record<string, string>;
} = $props();

let expandedCrn: string | null = $state(null);
let tableWrapper: HTMLDivElement = undefined!;

onMount(() => {
  const osInstance = OverlayScrollbars(tableWrapper, {
    overflow: { x: "scroll", y: "hidden" },
    scrollbars: {
      autoHide: "never",
      theme: themeStore.isDark ? "os-theme-dark" : "os-theme-light",
    },
  });

  // React to theme changes
  const unwatch = $effect.root(() => {
    $effect(() => {
      osInstance.options({
        scrollbars: {
          theme: themeStore.isDark ? "os-theme-dark" : "os-theme-light",
        },
      });
    });
  });

  return () => {
    unwatch();
    osInstance.destroy();
  };
});

// Column visibility state
let columnVisibility: VisibilityState = $state({});

const DEFAULT_VISIBILITY: VisibilityState = {};

function resetColumnVisibility() {
  columnVisibility = { ...DEFAULT_VISIBILITY };
}

function handleVisibilityChange(updater: Updater<VisibilityState>) {
  const newVisibility = typeof updater === "function" ? updater(columnVisibility) : updater;
  columnVisibility = newVisibility;
}

// visibleColumnIds and hasCustomVisibility derived after column definitions below

function toggleRow(crn: string) {
  expandedCrn = expandedCrn === crn ? null : crn;
}

function openSeats(course: CourseResponse): number {
  return Math.max(0, course.maxEnrollment - course.enrollment);
}

function seatsColor(course: CourseResponse): string {
  const open = openSeats(course);
  if (open === 0) return "text-status-red";
  if (open <= 5) return "text-yellow-500";
  return "text-status-green";
}

function seatsDotColor(course: CourseResponse): string {
  const open = openSeats(course);
  if (open === 0) return "bg-red-500";
  if (open <= 5) return "bg-yellow-500";
  return "bg-green-500";
}

function primaryInstructorDisplay(course: CourseResponse): string {
  const primary = getPrimaryInstructor(course.instructors);
  if (!primary) return "Staff";
  return abbreviateInstructor(primary.displayName);
}

function ratingColor(rating: number): string {
  if (rating >= 4.0) return "text-status-green";
  if (rating >= 3.0) return "text-yellow-500";
  return "text-status-red";
}

function primaryRating(course: CourseResponse): { rating: number; count: number } | null {
  const primary = getPrimaryInstructor(course.instructors);
  if (!primary?.rmpRating) return null;
  return { rating: primary.rmpRating, count: primary.rmpNumRatings ?? 0 };
}

function timeIsTBA(course: CourseResponse): boolean {
  if (course.meetingTimes.length === 0) return true;
  const mt = course.meetingTimes[0];
  return isMeetingTimeTBA(mt) && isTimeTBA(mt);
}

// Column definitions
const columns: ColumnDef<CourseResponse, unknown>[] = [
  {
    id: "crn",
    accessorKey: "crn",
    header: "CRN",
    enableSorting: false,
  },
  {
    id: "course_code",
    accessorFn: (row) => `${row.subject} ${row.courseNumber}`,
    header: "Course",
    enableSorting: true,
  },
  {
    id: "title",
    accessorKey: "title",
    header: "Title",
    enableSorting: true,
  },
  {
    id: "instructor",
    accessorFn: (row) => primaryInstructorDisplay(row),
    header: "Instructor",
    enableSorting: true,
  },
  {
    id: "time",
    accessorFn: (row) => {
      if (row.meetingTimes.length === 0) return "";
      const mt = row.meetingTimes[0];
      return `${formatMeetingDays(mt)} ${formatTime(mt.begin_time)}`;
    },
    header: "Time",
    enableSorting: true,
  },
  {
    id: "location",
    accessorFn: (row) => formatLocation(row) ?? "",
    header: "Location",
    enableSorting: false,
  },
  {
    id: "seats",
    accessorFn: (row) => openSeats(row),
    header: "Seats",
    enableSorting: true,
  },
];

/** Column IDs that are currently visible */
let visibleColumnIds = $derived(
  columns.map((c) => c.id!).filter((id) => columnVisibility[id] !== false)
);

let hasCustomVisibility = $derived(Object.values(columnVisibility).some((v) => v === false));

function handleSortingChange(updater: Updater<SortingState>) {
  const newSorting = typeof updater === "function" ? updater(sorting) : updater;
  onSortingChange?.(newSorting);
}

const table = createSvelteTable({
  get data() {
    return courses;
  },
  columns,
  state: {
    get sorting() {
      return sorting;
    },
    get columnVisibility() {
      return columnVisibility;
    },
  },
  onSortingChange: handleSortingChange,
  onColumnVisibilityChange: handleVisibilityChange,
  getCoreRowModel: getCoreRowModel(),
  get getSortedRowModel() {
    return manualSorting ? undefined : getSortedRowModel<CourseResponse>();
  },
  get manualSorting() {
    return manualSorting;
  },
  enableSortingRemoval: true,
});
</script>

{#snippet columnVisibilityItems(variant: "dropdown" | "context")}
  {#if variant === "dropdown"}
    <DropdownMenu.Group>
      <DropdownMenu.GroupHeading class="px-2 py-1.5 text-xs font-medium text-muted-foreground">
        Toggle columns
      </DropdownMenu.GroupHeading>
      {#each columns as col}
        {@const id = col.id!}
        {@const label = typeof col.header === "string" ? col.header : id}
        <DropdownMenu.CheckboxItem
          checked={columnVisibility[id] !== false}
          closeOnSelect={false}
          onCheckedChange={(checked) => {
            columnVisibility = { ...columnVisibility, [id]: checked };
          }}
          class="relative flex items-center gap-2 rounded-sm px-2 py-1.5 text-sm cursor-pointer select-none outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground"
        >
          {#snippet children({ checked })}
            <span class="flex size-4 items-center justify-center rounded-sm border border-border">
              {#if checked}
                <Check class="size-3" />
              {/if}
            </span>
            {label}
          {/snippet}
        </DropdownMenu.CheckboxItem>
      {/each}
    </DropdownMenu.Group>
    {#if hasCustomVisibility}
      <DropdownMenu.Separator class="mx-1 my-1 h-px bg-border" />
      <DropdownMenu.Item
        class="flex items-center gap-2 rounded-sm px-2 py-1.5 text-sm cursor-pointer select-none outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground"
        onSelect={resetColumnVisibility}
      >
        <RotateCcw class="size-3.5" />
        Reset to default
      </DropdownMenu.Item>
    {/if}
  {:else}
    <ContextMenu.Group>
      <ContextMenu.GroupHeading class="px-2 py-1.5 text-xs font-medium text-muted-foreground">
        Toggle columns
      </ContextMenu.GroupHeading>
      {#each columns as col}
        {@const id = col.id!}
        {@const label = typeof col.header === "string" ? col.header : id}
        <ContextMenu.CheckboxItem
          checked={columnVisibility[id] !== false}
          closeOnSelect={false}
          onCheckedChange={(checked) => {
            columnVisibility = { ...columnVisibility, [id]: checked };
          }}
          class="relative flex items-center gap-2 rounded-sm px-2 py-1.5 text-sm cursor-pointer select-none outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground"
        >
          {#snippet children({ checked })}
            <span class="flex size-4 items-center justify-center rounded-sm border border-border">
              {#if checked}
                <Check class="size-3" />
              {/if}
            </span>
            {label}
          {/snippet}
        </ContextMenu.CheckboxItem>
      {/each}
    </ContextMenu.Group>
    {#if hasCustomVisibility}
      <ContextMenu.Separator class="mx-1 my-1 h-px bg-border" />
      <ContextMenu.Item
        class="flex items-center gap-2 rounded-sm px-2 py-1.5 text-sm cursor-pointer select-none outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground"
        onSelect={resetColumnVisibility}
      >
        <RotateCcw class="size-3.5" />
        Reset to default
      </ContextMenu.Item>
    {/if}
  {/if}
{/snippet}

<!-- Toolbar: View columns button -->
<div class="flex items-center justify-end pb-2">
  <DropdownMenu.Root>
    <Tooltip.Root delayDuration={150} disableHoverableContent>
      <Tooltip.Trigger>
        <DropdownMenu.Trigger
          class="inline-flex items-center gap-1.5 rounded-md border border-border bg-background px-2.5 py-1.5 text-xs font-medium text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors cursor-pointer"
        >
          <Columns3 class="size-3.5" />
          View
        </DropdownMenu.Trigger>
      </Tooltip.Trigger>
      <Tooltip.Content
        side="bottom"
        sideOffset={6}
        class="z-50 bg-card text-card-foreground text-xs border border-border rounded-md px-2.5 py-1.5 shadow-md"
      >
        Show or hide table columns
      </Tooltip.Content>
    </Tooltip.Root>

    <DropdownMenu.Portal>
      <DropdownMenu.Content
        class="z-50 min-w-[160px] rounded-md border border-border bg-card p-1 text-card-foreground shadow-lg"
        align="end"
        sideOffset={4}
        forceMount
      >
        {#snippet child({ wrapperProps, props, open })}
          {#if open}
            <div {...wrapperProps}>
              <div {...props} transition:fly={{ duration: 150, y: -10 }}>
                {@render columnVisibilityItems("dropdown")}
              </div>
            </div>
          {/if}
        {/snippet}
      </DropdownMenu.Content>
    </DropdownMenu.Portal>
  </DropdownMenu.Root>
</div>

<!-- Table with context menu on header -->
<div bind:this={tableWrapper} class="overflow-x-auto">
  <ContextMenu.Root>
    <ContextMenu.Trigger class="contents">
      <table class="w-full min-w-[640px] border-collapse text-sm">
        <thead>
          {#each table.getHeaderGroups() as headerGroup}
            <tr class="border-b border-border text-left text-muted-foreground">
              {#each headerGroup.headers as header}
                {#if header.column.getIsVisible()}
                  <th
                    class="py-2 px-2 font-medium {header.id === 'seats' ? 'text-right' : ''}"
                    class:cursor-pointer={header.column.getCanSort()}
                    class:select-none={header.column.getCanSort()}
                    onclick={header.column.getToggleSortingHandler()}
                  >
                    {#if header.column.getCanSort()}
                      <span class="inline-flex items-center gap-1">
                        {#if typeof header.column.columnDef.header === "string"}
                          {header.column.columnDef.header}
                        {:else}
                          <FlexRender content={header.column.columnDef.header} context={header.getContext()} />
                        {/if}
                        {#if header.column.getIsSorted() === "asc"}
                          <ArrowUp class="size-3.5" />
                        {:else if header.column.getIsSorted() === "desc"}
                          <ArrowDown class="size-3.5" />
                        {:else}
                          <ArrowUpDown class="size-3.5 text-muted-foreground/40" />
                        {/if}
                      </span>
                    {:else if typeof header.column.columnDef.header === "string"}
                      {header.column.columnDef.header}
                    {:else}
                      <FlexRender content={header.column.columnDef.header} context={header.getContext()} />
                    {/if}
                  </th>
                {/if}
              {/each}
            </tr>
          {/each}
        </thead>
        <tbody>
          {#if loading && courses.length === 0}
            {#each Array(5) as _}
              <tr class="border-b border-border">
                {#each table.getVisibleLeafColumns() as col}
                  <td class="py-2.5 px-2">
                    <div class="h-4 bg-muted rounded animate-pulse {col.id === 'seats' ? 'w-14 ml-auto' : col.id === 'title' ? 'w-40' : col.id === 'crn' ? 'w-10' : 'w-20'}"></div>
                  </td>
                {/each}
              </tr>
            {/each}
          {:else if courses.length === 0}
            <tr>
              <td colspan={visibleColumnIds.length} class="py-12 text-center text-muted-foreground">
                No courses found. Try adjusting your filters.
              </td>
            </tr>
          {:else}
            {#each table.getRowModel().rows as row (row.id)}
              {@const course = row.original}
              <tr
                class="border-b border-border cursor-pointer hover:bg-muted/50 transition-colors whitespace-nowrap {expandedCrn === course.crn ? 'bg-muted/30' : ''}"
                onclick={() => toggleRow(course.crn)}
              >
                {#each row.getVisibleCells() as cell (cell.id)}
                  {@const colId = cell.column.id}
                  {#if colId === "crn"}
                    <td class="py-2 px-2 font-mono text-xs text-muted-foreground/70">{course.crn}</td>
                  {:else if colId === "course_code"}
                    {@const subjectDesc = subjectMap[course.subject]}
                    <td class="py-2 px-2 whitespace-nowrap">
                      <SimpleTooltip text={subjectDesc ? `${subjectDesc} ${course.courseNumber}` : `${course.subject} ${course.courseNumber}`} delay={200} side="bottom" passthrough>
                        <span class="font-semibold">{course.subject} {course.courseNumber}</span>{#if course.sequenceNumber}<span class="text-muted-foreground">-{course.sequenceNumber}</span>{/if}
                      </SimpleTooltip>
                    </td>
                  {:else if colId === "title"}
                    <td class="py-2 px-2 font-medium max-w-[200px] truncate">
                      <SimpleTooltip text={course.title} delay={200} side="bottom" passthrough>
                        <span class="block truncate">{course.title}</span>
                      </SimpleTooltip>
                    </td>
                  {:else if colId === "instructor"}
                    {@const primary = getPrimaryInstructor(course.instructors)}
                    <td class="py-2 px-2 whitespace-nowrap">
                      <SimpleTooltip text={primary?.displayName ?? "Staff"} delay={200} side="bottom" passthrough>
                        <span>{primaryInstructorDisplay(course)}</span>
                      </SimpleTooltip>
                      {#if primaryRating(course)}
                        {@const r = primaryRating(course)!}
                        <SimpleTooltip text="{r.rating.toFixed(1)}/5 ({r.count} ratings on RateMyProfessors)" delay={150} side="bottom" passthrough>
                          <span
                            class="ml-1 text-xs font-medium {ratingColor(r.rating)}"
                          >{r.rating.toFixed(1)}★</span>
                        </SimpleTooltip>
                      {/if}
                    </td>
                  {:else if colId === "time"}
                    <td class="py-2 px-2 whitespace-nowrap">
                      {#if timeIsTBA(course)}
                        <span class="text-xs text-muted-foreground/60">TBA</span>
                      {:else}
                        {@const mt = course.meetingTimes[0]}
                        {#if !isMeetingTimeTBA(mt)}
                          <span class="font-mono font-medium">{formatMeetingDays(mt)}</span>
                          {" "}
                        {/if}
                        {#if !isTimeTBA(mt)}
                          <span class="text-muted-foreground">{formatTime(mt.begin_time)}&ndash;{formatTime(mt.end_time)}</span>
                        {:else}
                          <span class="text-xs text-muted-foreground/60">TBA</span>
                        {/if}
                      {/if}
                    </td>
                  {:else if colId === "location"}
                    <td class="py-2 px-2 whitespace-nowrap">
                      {#if formatLocation(course)}
                        <span class="text-muted-foreground">{formatLocation(course)}</span>
                      {:else}
                        <span class="text-xs text-muted-foreground/50">—</span>
                      {/if}
                    </td>
                  {:else if colId === "seats"}
                    <td class="py-2 px-2 text-right whitespace-nowrap">
                      <SimpleTooltip text="{openSeats(course)} of {course.maxEnrollment} seats open, {course.enrollment} enrolled{course.waitCount > 0 ? `, ${course.waitCount} waitlisted` : ''}" delay={200} side="left" passthrough>
                        <span class="inline-flex items-center gap-1.5">
                          <span class="size-1.5 rounded-full {seatsDotColor(course)} shrink-0"></span>
                          <span class="{seatsColor(course)} font-medium tabular-nums">{#if openSeats(course) === 0}Full{:else}{openSeats(course)} open{/if}</span>
                          <span class="text-muted-foreground/60 tabular-nums">{course.enrollment}/{course.maxEnrollment}{#if course.waitCount > 0} · WL {course.waitCount}/{course.waitCapacity}{/if}</span>
                        </span>
                      </SimpleTooltip>
                    </td>
                  {/if}
                {/each}
              </tr>
              {#if expandedCrn === course.crn}
                <tr>
                  <td colspan={visibleColumnIds.length} class="p-0">
                    <div transition:slide={{ duration: 200 }}>
                      <CourseDetail {course} />
                    </div>
                  </td>
                </tr>
              {/if}
            {/each}
          {/if}
        </tbody>
      </table>
    </ContextMenu.Trigger>
    <ContextMenu.Portal>
      <ContextMenu.Content
        class="z-50 min-w-[160px] rounded-md border border-border bg-card p-1 text-card-foreground shadow-lg"
        forceMount
      >
        {#snippet child({ wrapperProps, props, open })}
          {#if open}
            <div {...wrapperProps}>
              <div {...props} in:fade={{ duration: 100 }} out:fade={{ duration: 100 }}>
                {@render columnVisibilityItems("context")}
              </div>
            </div>
          {/if}
        {/snippet}
      </ContextMenu.Content>
    </ContextMenu.Portal>
  </ContextMenu.Root>
</div>
