<template>
  <Teleport to="body">
    <!-- Bottom banner — shown until visitor makes a decision. -->
    <Transition
      enter-active-class="transition duration-200 ease-out"
      enter-from-class="translate-y-full opacity-0"
      enter-to-class="translate-y-0 opacity-100"
      leave-active-class="transition duration-150 ease-in"
      leave-from-class="translate-y-0 opacity-100"
      leave-to-class="translate-y-full opacity-0"
    >
      <div
        v-if="bannerVisible"
        class="x-cookie-consent fixed inset-x-0 bottom-0 z-50 border-t border-neutral-200 bg-white shadow-2xl dark:border-neutral-800 dark:bg-neutral-950"
        role="dialog"
        aria-label="Cookie consent"
      >
        <div class="mx-auto flex max-w-6xl flex-col gap-3 p-4 sm:flex-row sm:items-center sm:p-6">
          <div class="flex-1 text-sm text-neutral-700 dark:text-neutral-300">
            <p class="font-semibold text-neutral-900 dark:text-white">
              {{ title }}
            </p>
            <p class="mt-1">
              {{ message }}
              <NuxtLink
                v-if="policyUrl"
                :to="policyUrl"
                class="text-primary hover:underline"
              >
                {{ policyLabel }}
              </NuxtLink>
            </p>
          </div>
          <div class="flex flex-wrap gap-2">
            <UButton
              v-if="showCustomize"
              color="neutral"
              variant="ghost"
              size="sm"
              @click="prefsOpen = true"
            >
              Customize
            </UButton>
            <UButton
              color="neutral"
              variant="outline"
              size="sm"
              @click="onRejectAll"
            >
              {{ rejectLabel }}
            </UButton>
            <UButton color="primary" size="sm" @click="onAcceptAll">
              {{ acceptLabel }}
            </UButton>
          </div>
        </div>
      </div>
    </Transition>

    <!-- Preferences modal — shown when visitor clicks "Customize". -->
    <UModal v-model:open="prefsOpen">
      <template #content>
        <div class="p-6">
          <header class="mb-6">
            <h3 class="text-xl font-semibold text-neutral-900 dark:text-white">
              {{ prefsTitle }}
            </h3>
            <p class="mt-2 text-sm text-neutral-600 dark:text-neutral-400">
              {{ prefsDescription }}
            </p>
          </header>

          <div class="space-y-4">
            <div
              v-for="category in effectiveCategories"
              :key="category.id"
              class="rounded-xl border border-neutral-200 p-4 dark:border-neutral-700"
            >
              <div class="flex items-start justify-between gap-4">
                <div class="flex-1">
                  <div class="flex items-center gap-2">
                    <h4 class="font-medium text-neutral-900 dark:text-white">
                      {{ category.label }}
                    </h4>
                    <UBadge
                      v-if="category.required"
                      label="Required"
                      size="xs"
                      variant="subtle"
                    />
                  </div>
                  <p class="mt-1 text-sm text-neutral-600 dark:text-neutral-400">
                    {{ category.description }}
                  </p>
                </div>
                <USwitch
                  :model-value="selections[category.id]"
                  :disabled="category.required"
                  @update:model-value="(val: boolean) => (selections[category.id] = val)"
                />
              </div>
            </div>
          </div>

          <footer class="mt-6 flex flex-col gap-3 sm:flex-row">
            <UButton
              :label="rejectLabel"
              variant="ghost"
              color="neutral"
              class="flex-1"
              @click="onRejectAll"
            />
            <UButton
              :label="saveLabel"
              variant="outline"
              class="flex-1"
              @click="onSaveSelected"
            />
            <UButton :label="acceptLabel" class="flex-1" @click="onAcceptAll" />
          </footer>

          <div
            v-if="policyUrl || privacyUrl"
            class="mt-4 border-t border-neutral-200 pt-4 text-center dark:border-neutral-700"
          >
            <div class="flex justify-center gap-4 text-sm">
              <NuxtLink
                v-if="privacyUrl"
                :to="privacyUrl"
                class="text-primary hover:underline"
              >
                Privacy Policy
              </NuxtLink>
              <NuxtLink
                v-if="policyUrl"
                :to="policyUrl"
                class="text-primary hover:underline"
              >
                Cookie Policy
              </NuxtLink>
            </div>
          </div>
        </div>
      </template>
    </UModal>
  </Teleport>
</template>

<script setup lang="ts">
type CategoryId = "necessary" | "analytics" | "marketing" | "preferences";

const DEFAULT_SELECTIONS: Record<CategoryId, boolean> = {
  necessary: false,
  analytics: false,
  marketing: false,
  preferences: false,
};

interface Category {
  id: CategoryId;
  label: string;
  description: string;
  required?: boolean;
  /** Default toggle state on first show. Ignored when `required: true`. */
  enabledByDefault?: boolean;
}

const props = withDefaults(
  defineProps<{
    /** Banner heading. */
    title?: string;
    /** Banner body text. */
    message?: string;
    /** Label on the Accept button. */
    acceptLabel?: string;
    /** Label on the Reject button. */
    rejectLabel?: string;
    /** Label on the Save Preferences button (in modal). */
    saveLabel?: string;
    /** Label of the policy link. */
    policyLabel?: string;
    /** URL of the cookie policy page. */
    policyUrl?: string;
    /** URL of the privacy policy page. */
    privacyUrl?: string;
    /** Modal title. */
    prefsTitle?: string;
    /** Modal description. */
    prefsDescription?: string;
    /** Show the Customize button in the banner. */
    showCustomize?: boolean;
    /** Categories shown in the preferences modal. */
    categories?: Category[];
    /** Cookie storage key. Override if you ship multiple consent UIs. */
    storageKey?: string;
    /** Force show the banner (e.g. via a "Manage cookies" link). */
    forceShow?: boolean;
  }>(),
  {
    title: "We use cookies",
    message:
      "We use cookies to enhance your browsing experience, analyze traffic, and improve our service. You can choose which categories to allow.",
    acceptLabel: "Accept all",
    rejectLabel: "Reject all",
    saveLabel: "Save preferences",
    policyLabel: "Read our policy",
    prefsTitle: "Cookie preferences",
    prefsDescription:
      "Choose which categories of cookies you allow. You can change this anytime.",
    showCustomize: true,
    storageKey: "xMarketing.consent",
    categories: () => [
      {
        id: "necessary",
        label: "Strictly necessary",
        description:
          "Required for the site to function (auth, security, basic page state). Cannot be disabled.",
        required: true,
        enabledByDefault: true,
      },
      {
        id: "analytics",
        label: "Analytics",
        description:
          "Helps us understand how visitors interact with the site so we can improve it.",
        enabledByDefault: false,
      },
      {
        id: "marketing",
        label: "Marketing",
        description:
          "Used to deliver personalized ads and measure campaign effectiveness.",
        enabledByDefault: false,
      },
      {
        id: "preferences",
        label: "Preferences",
        description: "Remembers your settings such as dark mode and locale.",
        enabledByDefault: false,
      },
    ],
  },
);

const emit = defineEmits<{
  /** Emitted with the granted categories when the visitor accepts all. */
  (e: "accept", state: Record<CategoryId, boolean>): void;
  /** Emitted with the granted categories when the visitor rejects all. */
  (e: "reject", state: Record<CategoryId, boolean>): void;
  /** Emitted with the granted categories on Save Preferences. */
  (e: "save", state: Record<CategoryId, boolean>): void;
}>();

const tracking = useConsentTracking();

// The banner is visible if:
//  - The parent forces it (forceShow), OR
//  - The visitor hasn't made a decision yet (no localStorage key).
const bannerVisible = ref(false);
const prefsOpen = ref(false);
const selections = ref<Record<CategoryId, boolean>>({ ...DEFAULT_SELECTIONS });

const effectiveCategories = computed(() => props.categories);

function buildStateFromSelections(): Record<CategoryId, boolean> {
  const out: Record<CategoryId, boolean> = { ...DEFAULT_SELECTIONS };
  for (const c of props.categories) {
    out[c.id] = c.required ? true : Boolean(selections.value[c.id]);
  }
  return out;
}

function buildStateAll(value: boolean): Record<CategoryId, boolean> {
  const out: Record<CategoryId, boolean> = { ...DEFAULT_SELECTIONS };
  for (const c of props.categories) {
    out[c.id] = c.required ? true : value;
  }
  return out;
}

function initSelections() {
  const next: Record<CategoryId, boolean> = { ...DEFAULT_SELECTIONS };
  for (const c of props.categories) {
    next[c.id] = c.required
      ? true
      : Boolean(c.enabledByDefault ?? false);
  }
  selections.value = next;
}

function syncFromStored() {
  if (typeof window === "undefined") return false;
  const raw = window.localStorage.getItem(props.storageKey);
  if (!raw) return false;
  try {
    const parsed = JSON.parse(raw);
    for (const c of props.categories) {
      selections.value[c.id] = c.required
        ? true
        : Boolean(parsed[c.id] ?? c.enabledByDefault ?? false);
    }
    return true;
  } catch {
    return false;
  }
}

onMounted(() => {
  if (typeof window === "undefined") return;
  // Defer so we don't fight hydration.
  queueMicrotask(() => {
    const hadDecision = syncFromStored();
    if (!hadDecision || props.forceShow) {
      initSelections();
      bannerVisible.value = true;
    }
  });
});

// If `forceShow` flips to true at runtime, show the banner.
watch(
  () => props.forceShow,
  (v) => {
    if (v) bannerVisible.value = true;
  },
);

function persistAndBroadcast(state: Record<CategoryId, boolean>) {
  if (typeof window === "undefined") return;
  // Use the composable to handle storage + event dispatch + injection.
  // We pass a shallow copy that matches ConsentState shape.
  tracking.setConsent({
    necessary: true,
    analytics: Boolean(state.analytics),
    marketing: Boolean(state.marketing),
    preferences: Boolean(state.preferences),
  });
}

function onAcceptAll() {
  const state = buildStateAll(true);
  for (const k of Object.keys(state)) {
    selections.value[k as CategoryId] = state[k as CategoryId];
  }
  persistAndBroadcast(state);
  bannerVisible.value = false;
  prefsOpen.value = false;
  emit("accept", state);
}

function onRejectAll() {
  const state = buildStateAll(false);
  for (const k of Object.keys(state)) {
    selections.value[k as CategoryId] = state[k as CategoryId];
  }
  persistAndBroadcast(state);
  bannerVisible.value = false;
  prefsOpen.value = false;
  emit("reject", state);
}

function onSaveSelected() {
  const state = buildStateFromSelections();
  persistAndBroadcast(state);
  bannerVisible.value = false;
  prefsOpen.value = false;
  emit("save", state);
}
</script>

<style scoped>
.x-cookie-consent {
  /* Sit above any sticky footer/CTA; matches the affiliate layer's banner. */
  z-index: 50;
}
</style>