<script lang="ts">
  import { onMount } from "svelte";
  import { fade, fly } from "svelte/transition";
  import browser from "webextension-polyfill";
  import {
    GOOGLE_CALENDAR_SYNC_WEEKS_MAX,
    GOOGLE_CALENDAR_SYNC_WEEKS_MIN,
  } from "@/config/googleCalendar";
  import { maybeRunDueWeeklySync } from "@/seqta/utils/googleCalendar/calendarSyncListener";
  import { deleteSyncedEventsFromGoogleCalendar } from "@/seqta/utils/googleCalendar/syncEngine";
  import {
    formatSyncResultMessage,
    runGoogleCalendarSync,
  } from "@/seqta/utils/googleCalendar/syncRunner";
  import type {
    GoogleCalendarStatus,
    GoogleCalendarSyncProgress,
    GoogleCalendarSyncResult,
  } from "@/seqta/utils/googleCalendar/types";
  import CalendarDeleteEventsModal from "./CalendarDeleteEventsModal.svelte";
  import CalendarDisconnectModal from "./CalendarDisconnectModal.svelte";
  import CalendarSyncProgress from "./CalendarSyncProgress.svelte";
  import { settingsState } from "@/seqta/utils/listeners/SettingsState";
  import { syncCalendarSyncTheme } from "./calendarSyncTheme";

  type BusyPhase = "connect" | "sync" | "delete" | "disconnect" | null;

  let status = $state<GoogleCalendarStatus>({ configured: true, connected: false });
  let busy = $state<BusyPhase>(null);
  let menuOpen = $state(false);
  let showDisconnect = $state(false);
  let showDeleteEvents = $state(false);
  let toast = $state<{ message: string; error: boolean } | null>(null);
  let syncProgress = $state<GoogleCalendarSyncProgress | null>(null);
  let syncWeeksAhead = $state(12);
  let autoSyncWeekly = $state(true);

  let rootEl = $state<HTMLDivElement | null>(null);
  let triggerEl = $state<HTMLButtonElement | null>(null);
  let menuEl = $state<HTMLDivElement | null>(null);
  let menuStyle = $state("");
  let toastTimer: ReturnType<typeof setTimeout> | null = null;

  const isBusy = $derived(busy !== null);
  const accent = "var(--bsplus-cal-accent, var(--better-main, #3b82f6))";

  function showToastMessage(message: string, isError = false) {
    toast = { message, error: isError };
    if (toastTimer) clearTimeout(toastTimer);
    toastTimer = setTimeout(() => {
      toast = null;
    }, 4500);
  }

  async function refreshStatus() {
    status = (await browser.runtime.sendMessage({
      type: "googleCalendarStatus",
    })) as GoogleCalendarStatus;
    syncWeeksAhead = status.syncWeeksAhead ?? 12;
    autoSyncWeekly = status.autoSyncWeekly !== false;
  }

  async function getAccessTokenFromBackground(): Promise<string> {
    const res = (await browser.runtime.sendMessage({
      type: "googleCalendarGetAccessToken",
    })) as { success?: boolean; accessToken?: string; error?: string };
    if (!res?.success || !res.accessToken) {
      throw new Error(res?.error ?? "Could not get Google Calendar access token.");
    }
    return res.accessToken;
  }

  function handleSyncProgress(progress: GoogleCalendarSyncProgress) {
    syncProgress = progress;
  }

  async function saveSyncSettings(patch: {
    syncWeeksAhead?: number;
    autoSyncWeekly?: boolean;
  }) {
    const result = (await browser.runtime.sendMessage({
      type: "googleCalendarUpdateSyncSettings",
      ...patch,
    })) as GoogleCalendarStatus & { success?: boolean };
    if (result.syncWeeksAhead != null) syncWeeksAhead = result.syncWeeksAhead;
    if (result.autoSyncWeekly != null) autoSyncWeekly = result.autoSyncWeekly;
    status = { ...status, ...result };
  }

  async function performSync(mode: "full" | "incremental" = "full"): Promise<boolean> {
    const result = await runGoogleCalendarSync({
      mode,
      onProgress: handleSyncProgress,
    });

    syncProgress = null;

    if (!result.success) {
      showToastMessage(result.error ?? "Calendar sync failed.", true);
      return false;
    }

    status = {
      ...status,
      connected: true,
      lastSyncAt: result.lastSyncAt ?? status.lastSyncAt,
    };
    showToastMessage(formatSyncResultMessage(result));
    return true;
  }

  async function connectGoogle() {
    if (!status.configured || isBusy) return;
    menuOpen = true;
    busy = "connect";
    try {
      const result = (await browser.runtime.sendMessage({
        type: "googleCalendarConnect",
      })) as GoogleCalendarSyncResult;
      if (!result.success) {
        showToastMessage(result.error ?? "Could not connect to Google Calendar.", true);
        return;
      }
      status = { ...status, connected: true };
      busy = "sync";
      await performSync();
    } catch (err) {
      showToastMessage(err instanceof Error ? err.message : "Could not connect.", true);
    } finally {
      syncProgress = null;
      busy = null;
    }
  }

  async function syncTimetable() {
    if (!status.configured || isBusy) return;
    if (!status.connected) {
      await connectGoogle();
      return;
    }

    busy = "sync";
    try {
      await performSync();
    } catch (err) {
      showToastMessage(err instanceof Error ? err.message : "Calendar sync failed.", true);
    } finally {
      syncProgress = null;
      busy = null;
    }
  }

  async function confirmDeleteEvents() {
    if (isBusy) return;
    busy = "delete";
    syncProgress = {
      phase: "preparing",
      current: 0,
      total: 1,
      message: "Preparing removal…",
    };
    try {
      const result = await deleteSyncedEventsFromGoogleCalendar(
        location.origin,
        getAccessTokenFromBackground,
        { onProgress: handleSyncProgress },
      );

      if (!result.success) {
        showToastMessage(result.error ?? "Could not remove calendar events.", true);
        return;
      }

      const removed = result.deleted ?? 0;
      showDeleteEvents = false;
      menuOpen = false;
      if (removed === 0) {
        showToastMessage("No synced events to remove.");
      } else {
        showToastMessage(`Removed ${removed} event${removed === 1 ? "" : "s"} from Google Calendar.`);
      }
    } catch (err) {
      showToastMessage(err instanceof Error ? err.message : "Remove failed.", true);
    } finally {
      syncProgress = null;
      busy = null;
    }
  }

  async function onWeeksAheadChange(event: Event) {
    const value = Number((event.currentTarget as HTMLInputElement).value);
    if (!Number.isFinite(value)) return;
    await saveSyncSettings({ syncWeeksAhead: value });
  }

  async function onAutoSyncToggle(event: Event) {
    const checked = (event.currentTarget as HTMLInputElement).checked;
    autoSyncWeekly = checked;
    await saveSyncSettings({ autoSyncWeekly: checked });
  }

  async function confirmDisconnect() {
    if (isBusy) return;
    busy = "disconnect";
    try {
      const result = (await browser.runtime.sendMessage({
        type: "googleCalendarDisconnect",
      })) as { success?: boolean };
      if (!result?.success) {
        showToastMessage("Could not disconnect Google Calendar.", true);
        return;
      }
      status = { ...status, connected: false, lastSyncAt: undefined };
      showDisconnect = false;
      menuOpen = false;
      showToastMessage("Disconnected from Google Calendar.");
    } catch (err) {
      showToastMessage(err instanceof Error ? err.message : "Disconnect failed.", true);
    } finally {
      busy = null;
    }
  }

  function toggleMenu() {
    if (isBusy) return;
    menuOpen = !menuOpen;
    if (menuOpen) {
      queueMicrotask(() => syncMenuTheme());
    }
  }

  function formatLastSync(ts?: number): string | null {
    if (!ts) return null;
    const diff = Date.now() - ts;
    if (diff < 60_000) return "Synced just now";
    if (diff < 3_600_000) return `Synced ${Math.floor(diff / 60_000)}m ago`;
    if (diff < 86_400_000) return `Synced ${Math.floor(diff / 3_600_000)}h ago`;
    return `Synced ${new Date(ts).toLocaleDateString()}`;
  }

  function updateMenuPosition() {
    if (!triggerEl) return;
    const rect = triggerEl.getBoundingClientRect();
    menuStyle = `top:${rect.bottom + 8}px;right:${window.innerWidth - rect.right}px;`;
    syncMenuTheme();
  }

  function syncMenuTheme() {
    if (!menuEl) return;
    syncCalendarSyncTheme(menuEl);
  }

  function syncMountedTheme() {
    const themeHost = rootEl?.closest(".bsplus-calendar-sync-mount") as HTMLElement | null;
    if (themeHost) syncCalendarSyncTheme(themeHost);
    if (menuOpen) syncMenuTheme();
  }

  function portalMenu(node: HTMLElement) {
    document.body.appendChild(node);
    return {
      destroy() {
        node.remove();
      },
    };
  }

  $effect(() => {
    const host = rootEl?.closest(".timetable-calendar-controls");
    host?.classList.toggle("bsplus-cal-menu-open", menuOpen);
    return () => host?.classList.remove("bsplus-cal-menu-open");
  });

  $effect(() => {
    if (!menuOpen || !menuEl) return;
    syncMenuTheme();
    updateMenuPosition();
    const onLayout = () => updateMenuPosition();
    window.addEventListener("resize", onLayout);
    window.addEventListener("scroll", onLayout, true);
    return () => {
      window.removeEventListener("resize", onLayout);
      window.removeEventListener("scroll", onLayout, true);
    };
  });

  onMount(() => {
    void refreshStatus().then(() => {
      void maybeRunDueWeeklySync((message, isError) => {
        showToastMessage(message, isError);
        void refreshStatus();
      });
    });

    const themeKeys = [
      "selectedColor",
      "DarkMode",
      "adaptiveThemeColour",
      "adaptiveThemeGradient",
      "selectedTheme",
    ] as const;
    const onThemeChange = () => syncMountedTheme();
    for (const key of themeKeys) {
      settingsState.register(key, onThemeChange);
    }

    const themeObserver = new MutationObserver(onThemeChange);
    themeObserver.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ["style", "class"],
    });

    const onDocPointer = (event: PointerEvent) => {
      if (!menuOpen) return;
      const target = event.target as Node;
      if (rootEl?.contains(target)) return;
      if (menuEl?.contains(target)) return;
      menuOpen = false;
    };

    document.addEventListener("pointerdown", onDocPointer);
    return () => {
      document.removeEventListener("pointerdown", onDocPointer);
      for (const key of themeKeys) {
        settingsState.unregister(key, onThemeChange);
      }
      themeObserver.disconnect();
      if (toastTimer) clearTimeout(toastTimer);
    };
  });
</script>

<div class="bsplus-cal-sync" bind:this={rootEl}>
  <button
    type="button"
    class="uiButton bsplus-cal-trigger iconFamily"
    bind:this={triggerEl}
    class:bsplus-cal-trigger--open={menuOpen}
    class:bsplus-cal-trigger--connected={status.connected}
    class:bsplus-cal-trigger--busy={isBusy}
    aria-haspopup="menu"
    aria-expanded={menuOpen}
    aria-busy={isBusy}
    aria-label={status.connected ? "Google Calendar sync options" : "Connect Google Calendar"}
    onclick={() => {
      if (!isBusy) toggleMenu();
    }}
  >
    <span class="bsplus-cal-trigger-icon" aria-hidden="true">&#xe9cd;</span>
    <span class="bsplus-cal-trigger-text">Calendar</span>
    {#if status.connected}
      <span class="bsplus-cal-status-dot" aria-hidden="true"></span>
    {/if}
  </button>

  {#if menuOpen}
    <div
      class="bsplus-cal-menu"
      role="menu"
      bind:this={menuEl}
      style={menuStyle}
      use:portalMenu
      transition:fly={{ y: -6, duration: 160 }}
    >
      <div class="bsplus-cal-menu-header">
        <span class="bsplus-cal-menu-title">Calendar sync</span>
        <span class="bsplus-cal-menu-sub">Connect providers to sync your timetable</span>
      </div>

      <div class="bsplus-cal-provider" role="none">
        <div class="bsplus-cal-provider-row">
          <span class="bsplus-cal-provider-icon" aria-hidden="true">
            <svg viewBox="0 0 24 24">
              <path
                fill="#4285F4"
                d="M22 12c0-.96-.08-1.88-.24-2.76H12v5.22h5.68c-.24 1.28-.96 2.44-2.04 3.18v2.64h3.3c1.92-1.76 3.06-4.36 3.06-7.28z"
              />
              <path
                fill="#34A853"
                d="M12 22c2.76 0 5.08-.92 6.78-2.5l-3.3-2.64c-.92.62-2.1.98-3.48.98-2.68 0-4.96-1.8-5.78-4.22H2.18v2.72A10 10 0 0 0 12 22z"
              />
              <path
                fill="#FBBC05"
                d="M6.22 13.62A5.98 5.98 0 0 1 5.82 12c0-.56.1-1.1.28-1.62V7.66H2.18A10 10 0 0 0 2 12c0 1.62.38 3.16 1.06 4.52l3.16-2.9z"
              />
              <path
                fill="#EA4335"
                d="M12 5.38c1.5 0 2.84.52 3.9 1.54l2.92-2.92C17.08 2.34 14.76 1.2 12 1.2 7.54 1.2 3.72 3.94 2.18 7.66l4.04 3.14c.82-2.42 3.1-4.22 5.78-4.22z"
              />
            </svg>
          </span>
          <div class="bsplus-cal-provider-copy">
            <span class="bsplus-cal-provider-name">
              <span class="bsplus-google-word bsplus-google-word--sm">
                <span class="bsplus-google-g">G</span><span class="bsplus-google-o1">o</span><span class="bsplus-google-o2">o</span><span class="bsplus-google-g2">g</span><span class="bsplus-google-l">l</span><span class="bsplus-google-e">e</span>
              </span>
              <span> Calendar</span>
            </span>
            <span class="bsplus-cal-provider-status">
              {#if !status.configured}
                Not available in this build
              {:else if status.connected}
                Connected{formatLastSync(status.lastSyncAt) ? ` · ${formatLastSync(status.lastSyncAt)}` : ""}
              {:else}
                Not connected
              {/if}
            </span>
          </div>
        </div>

        {#if status.connected}
          <div class="bsplus-cal-settings" role="group" aria-label="Sync settings">
            <label class="bsplus-cal-setting">
              <span class="bsplus-cal-setting-label">Weeks ahead</span>
              <input
                type="number"
                class="bsplus-cal-setting-input"
                min={GOOGLE_CALENDAR_SYNC_WEEKS_MIN}
                max={GOOGLE_CALENDAR_SYNC_WEEKS_MAX}
                value={syncWeeksAhead}
                disabled={isBusy}
                onchange={(e) => void onWeeksAheadChange(e)}
              />
            </label>
            <label class="bsplus-cal-setting bsplus-cal-setting--toggle">
              <span class="bsplus-cal-setting-label">Auto-sync weekly</span>
              <input
                type="checkbox"
                class="bsplus-cal-setting-checkbox"
                checked={autoSyncWeekly}
                disabled={isBusy}
                onchange={(e) => void onAutoSyncToggle(e)}
              />
            </label>
            <p class="bsplus-cal-setting-hint">
              Keeps a rolling {syncWeeksAhead}-week window. Each week adds the next week and removes the oldest.
            </p>
          </div>
        {/if}

        <CalendarSyncProgress progress={syncProgress} />

        <div class="bsplus-cal-provider-actions">
          {#if !status.connected}
            <button
              type="button"
              class="bsplus-cal-action bsplus-cal-action--primary"
              style:--bsplus-cal-accent={accent}
              role="menuitem"
              disabled={!status.configured || isBusy}
              onclick={() => void connectGoogle()}
            >
              {busy === "connect" ? "Connecting…" : "Connect"}
            </button>
          {:else}
            <button
              type="button"
              class="bsplus-cal-action bsplus-cal-action--primary"
              style:--bsplus-cal-accent={accent}
              role="menuitem"
              disabled={isBusy}
              onclick={() => void syncTimetable()}
            >
              {busy === "sync" ? "Syncing…" : "Sync now"}
            </button>
            <button
              type="button"
              class="bsplus-cal-action bsplus-cal-action--ghost"
              role="menuitem"
              disabled={isBusy}
              onclick={() => {
                showDeleteEvents = true;
              }}
            >
              {busy === "delete" ? "Removing…" : "Remove from calendar"}
            </button>
            <button
              type="button"
              class="bsplus-cal-action bsplus-cal-action--ghost"
              role="menuitem"
              disabled={isBusy}
              onclick={() => {
                showDisconnect = true;
              }}
            >
              Disconnect
            </button>
          {/if}
        </div>
      </div>

      <div class="bsplus-cal-coming-soon" role="none">
        <span class="bsplus-cal-coming-soon-label">More providers</span>
        <span class="bsplus-cal-coming-soon-hint">Outlook, Apple Calendar — coming soon</span>
      </div>
    </div>
  {/if}

  <CalendarDeleteEventsModal
    open={showDeleteEvents}
    busy={busy === "delete"}
    onCancel={() => {
      if (busy !== "delete") showDeleteEvents = false;
    }}
    onConfirm={confirmDeleteEvents}
  />

  <CalendarDisconnectModal
    open={showDisconnect}
    busy={busy === "disconnect"}
    onCancel={() => {
      if (busy !== "disconnect") showDisconnect = false;
    }}
    onConfirm={confirmDisconnect}
  />

  {#if toast}
    <div
      class="bsplus-cal-toast"
      class:bsplus-cal-toast--error={toast.error}
      role="status"
      transition:fade={{ duration: 150 }}
    >
      {toast.message}
    </div>
  {/if}
</div>

<style>
  .bsplus-cal-sync {
    position: relative;
    display: inline-flex;
    font-family: Rubik, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    color: var(--bsplus-cal-text, var(--text-primary, #111));
  }

  .bsplus-cal-trigger {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-width: auto;
    height: auto;
    padding: 0 10px;
    margin-left: 4px;
    border-radius: 16px !important;
    transition: all 0.2s ease;
  }

  .bsplus-cal-trigger-icon {
    font-size: 16px;
    line-height: 1;
    opacity: 0.9;
  }

  .bsplus-cal-trigger-text {
    font-size: 13px;
    font-weight: 600;
    line-height: 1;
    white-space: nowrap;
  }

  .bsplus-google-word {
    display: inline-flex;
    font-size: 13px;
    font-weight: 700;
    letter-spacing: -0.02em;
  }

  .bsplus-google-word--sm {
    font-size: 13px;
  }

  .bsplus-google-g,
  .bsplus-google-g2 {
    color: #4285f4;
  }

  .bsplus-google-o1,
  .bsplus-google-e {
    color: #ea4335;
  }

  .bsplus-google-o2 {
    color: #fbbc05;
  }

  .bsplus-google-l {
    color: #34a853;
  }

  .bsplus-cal-trigger:hover:not(.bsplus-cal-trigger--busy) {
    transform: scale(1.03);
  }

  .bsplus-cal-trigger:active:not(.bsplus-cal-trigger--busy) {
    transform: scale(0.97);
  }

  .bsplus-cal-trigger--open {
    background: color-mix(in srgb, var(--bsplus-cal-accent, var(--better-main, #3b82f6)) 14%, transparent) !important;
  }

  .bsplus-cal-trigger--connected .bsplus-cal-status-dot {
    display: block;
  }

  .bsplus-cal-trigger--busy {
    opacity: 0.85;
    cursor: wait;
  }

  .bsplus-cal-status-dot {
    position: absolute;
    top: 4px;
    right: 4px;
    width: 7px;
    height: 7px;
    border-radius: 999px;
    background: #22c55e;
    box-shadow: 0 0 0 2px var(--bsplus-cal-surface, #fff);
    display: none;
  }

  .bsplus-cal-settings {
    margin: 8px 0 10px;
    padding: 10px;
    border-radius: 10px;
    border: 1px solid var(--bsplus-cal-border, color-mix(in srgb, var(--bsplus-cal-text) 12%, transparent));
    background: color-mix(in srgb, var(--bsplus-cal-surface, #fff) 92%, transparent);
    display: grid;
    gap: 8px;
  }

  .bsplus-cal-setting {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    font-size: 12px;
  }

  .bsplus-cal-setting-label {
    font-weight: 600;
    color: var(--bsplus-cal-text, var(--text-primary, #111));
  }

  .bsplus-cal-setting-input {
    width: 64px;
    padding: 6px 8px;
    border-radius: 10px;
    border: 1px solid var(--bsplus-cal-border, color-mix(in srgb, var(--bsplus-cal-text) 18%, transparent));
    background: var(--bsplus-cal-surface, #fff);
    color: var(--bsplus-cal-text, var(--text-primary, #111));
    font-size: 12px;
    text-align: center;
  }

  .bsplus-cal-setting-checkbox {
    width: 16px;
    height: 16px;
    accent-color: var(--bsplus-cal-accent, var(--better-main, #3b82f6));
  }

  .bsplus-cal-setting-hint {
    margin: 0;
    font-size: 10px;
    line-height: 1.4;
    color: color-mix(in srgb, var(--bsplus-cal-text, #111) 58%, transparent);
  }

  .bsplus-cal-menu {
    position: fixed;
    z-index: 2147483647;
    width: min(320px, calc(100vw - 24px));
    padding: 10px;
    border-radius: 14px;
    background: var(--bsplus-cal-surface, #fff);
    color: var(--bsplus-cal-text, #18181b);
    border: 1px solid var(--bsplus-cal-border, color-mix(in srgb, var(--bsplus-cal-text) 12%, transparent));
    box-shadow: 0 16px 40px rgba(0, 0, 0, 0.22);
    font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  }

  .bsplus-cal-menu.dark {
    box-shadow: 0 16px 40px rgba(0, 0, 0, 0.45);
  }

  .bsplus-cal-menu-header {
    padding: 6px 8px 10px;
    border-bottom: 1px solid var(--bsplus-cal-border, color-mix(in srgb, var(--bsplus-cal-text) 10%, transparent));
    margin-bottom: 8px;
  }

  .bsplus-cal-menu-title {
    display: block;
    font-size: 13px;
    font-weight: 700;
  }

  .bsplus-cal-menu-sub {
    display: block;
    margin-top: 2px;
    font-size: 11px;
    color: color-mix(in srgb, var(--bsplus-cal-text, #111) 62%, transparent);
  }

  .bsplus-cal-provider {
    padding: 8px;
    border-radius: 10px;
    background: var(--bsplus-cal-surface-muted, color-mix(in srgb, var(--bsplus-cal-text) 4%, transparent));
  }

  .bsplus-cal-provider-row {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 10px;
  }

  .bsplus-cal-provider-icon {
    display: flex;
    flex: 0 0 auto;
    width: 28px;
    height: 28px;
  }

  .bsplus-cal-provider-icon svg {
    width: 100%;
    height: 100%;
  }

  .bsplus-cal-provider-copy {
    min-width: 0;
  }

  .bsplus-cal-provider-name {
    display: block;
    font-size: 13px;
    font-weight: 600;
  }

  .bsplus-cal-provider-status {
    display: block;
    margin-top: 1px;
    font-size: 11px;
    color: color-mix(in srgb, var(--bsplus-cal-text, #111) 62%, transparent);
  }

  .bsplus-cal-provider-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
  }

  .bsplus-cal-action {
    flex: 1 1 auto;
    min-width: 0;
    padding: 7px 10px;
    border: none;
    border-radius: 8px;
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    transition: all 0.2s ease;
  }

  .bsplus-cal-action:disabled {
    opacity: 0.55;
    cursor: not-allowed;
  }

  .bsplus-cal-action--primary {
    background: var(--bsplus-cal-accent, var(--better-main, #3b82f6));
    color: #fff;
  }

  .bsplus-cal-action--primary:hover:not(:disabled) {
    filter: brightness(1.06);
    transform: scale(1.02);
  }

  .bsplus-cal-action--ghost {
    background: var(--bsplus-cal-surface-muted, color-mix(in srgb, var(--bsplus-cal-text) 8%, transparent));
    color: var(--bsplus-cal-text, #18181b);
  }

  .bsplus-cal-action--ghost:hover:not(:disabled) {
    background: color-mix(in srgb, var(--bsplus-cal-text) 14%, var(--bsplus-cal-surface));
  }

  .bsplus-cal-coming-soon {
    margin-top: 8px;
    padding: 8px 10px;
    border-radius: 8px;
    border: 1px dashed var(--bsplus-cal-border, color-mix(in srgb, var(--bsplus-cal-text) 18%, transparent));
  }

  .bsplus-cal-coming-soon-label {
    display: block;
    font-size: 11px;
    font-weight: 600;
    color: color-mix(in srgb, var(--bsplus-cal-text, #111) 70%, transparent);
  }

  .bsplus-cal-coming-soon-hint {
    display: block;
    margin-top: 2px;
    font-size: 10px;
    color: color-mix(in srgb, var(--bsplus-cal-text, #111) 50%, transparent);
  }

  .bsplus-cal-toast {
    position: fixed;
    right: 16px;
    bottom: 16px;
    z-index: 100000;
    max-width: min(360px, calc(100vw - 32px));
    padding: 12px 14px;
    border-radius: 12px;
    background: rgba(20, 20, 20, 0.92);
    color: #fff;
    font-size: 13px;
    line-height: 1.4;
    box-shadow: 0 12px 30px rgba(0, 0, 0, 0.25);
    pointer-events: none;
  }

  .bsplus-cal-toast--error {
    background: rgba(120, 24, 24, 0.95);
  }
</style>
