/// <reference types="astro/client" />
/// <reference types="@vite-pwa/astro/client" />
/// <reference types="@cloudflare/workers-types" />

import type { Services } from '@/server/services/types.js';
import type { VendorRow } from '@/server/middleware/session.js';
import type { TransactionCaseRow } from '@/server/db/queries/support-cases.js';
import type { CaseRole } from '@/server/auth/case-access.js';
import type { DealHit } from '@/server/db/queries/deal-translations.js';

declare global {
  // Injected by Vite define at build time (astro.config.mjs → BUILD_ID).
  declare const __BUILD_ID__: string;

  interface Window {
    VConsole?: new () => unknown;
  }

  // ---------------------------------------------------------------------------
  // Cloudflare Workers env bindings
  // ---------------------------------------------------------------------------

  interface Env {
    // Database
    DATABASE_URL: string;

    // Auth
    SESSION_SECRET: string;
    QR_SECRET: string;
    PII_KEY: string;

    // Morning payments
    MORNING_API_ID: string;
    MORNING_API_SECRET: string;
    MORNING_ENV: 'sandbox' | 'production';
    /** Shared secret for webhook signature verification (optional) */
    MORNING_WEBHOOK_SECRET?: string;

    // Email (Resend)
    RESEND_API_KEY: string;

    // Web Push (VAPID)
    VAPID_PUBLIC_KEY: string;
    VAPID_PRIVATE_KEY: string;
    VAPID_SUBJECT: string;

    // Cloudflare R2 bucket binding
    R2_BUCKET: R2Bucket;

    // Public URL
    PUBLIC_SITE_URL: string;

    // Observability
    SENTRY_DSN: string;

    // AI - Google Gemini
    // Set via: pnpm cf:secret GOOGLE_API_KEY
    GOOGLE_API_KEY?: string;

    // Durable Object namespace bindings (local to this worker)
    USER_SESSION_DO: DurableObjectNamespace;
    TOPIC_DO: DurableObjectNamespace;
  }

  // ---------------------------------------------------------------------------
  // Locale type
  // ---------------------------------------------------------------------------

  type MultidealLocale = 'he' | 'en';

  // ---------------------------------------------------------------------------
  // Astro App.Locals
  // ---------------------------------------------------------------------------

  declare namespace App {
    // These types mirror the Drizzle-inferred types from the sessions + users tables.
    // Must stay in sync with src/server/db/schema.ts.
    // Using | null (not | undefined) to match Drizzle's nullable column inference.
    type SessionRow = {
      id: string;
      userId: string;
      csrfToken: string;
      userAgent: string;
      ipEncrypted: string | null;
      expiresAt: Date;
      revokedAt: Date | null;
      createdAt: Date;
      /** SHA-256 hex of opaque refresh token. Added M1. */
      refreshTokenHash: string;
      /** Null until first refresh. Added M1. */
      lastRefreshedAt: Date | null;
      /** Date of email verification (sourced from JWT `evAt` claim). Null if unverified. */
      emailVerifiedAt: Date | null;
      /** Date of onboarding completion (sourced from JWT `obAt` claim). Null if not yet completed. */
      onboardingCompletedAt: Date | null;
    };

    type UserRow = {
      id: string;
      phone: string | null;
      phoneIndex: string | null;
      email: string | null;
      emailIndex: string | null;
      passwordHash: string | null;
      avatarType: 'ICON' | 'GRAVATAR' | 'UPLOADED';
      avatarValue: string;
      email2faEnabled: boolean;
      purchaseCount: number;
      preferencesProfile: unknown;
      accountState: 'ACTIVE' | 'FROZEN' | 'DELETED_PENDING' | 'DELETED';
      isAdmin: boolean;
      createdAt: Date;
      deletionRequestedAt: Date | null;
      /** Incremented on logout-all / password change. Added M1. */
      sessionVersion: number;
      displayName: string | null;
      /** Unix-timestamp of onboarding completion, or null if not yet completed. Added T1. */
      onboardingCompletedAt: Date | null;
      emailVerifiedAt: Date | null;
      /** mh-cookie cache invalidation counter. Incremented on profile/cart/wishlist mutations. */
      mhVersion: number;
      /** Avatar moderation state. */
      avatarApprovalStatus: 'PENDING' | 'APPROVED' | 'REJECTED';
      pendingAvatarValue: string | null;
      avatarRejectReasonCode: string | null;
      /** User's city (free-text display label). Null until set during onboarding. */
      city: string | null;
      /** Standardised city code for lookups/filtering. Null until set during onboarding. */
      cityCode: string | null;
      /** User-chosen preferred city code for feed filtering. Null = auto. */
      preferredCityCode: string | null;
      /** Birth month 1-12. Null until set during onboarding. */
      birthMonth: number | null;
      /** Birth day 1-31. Null until set during onboarding. */
      birthDay: number | null;
      /** Per-user notification preferences. Keys are event names; false = opted out. */
      notifPrefs: {
        optional?: Record<string, boolean>;
        marketing?: Record<string, boolean>;
      };
      /** User preference JSON. Schema column is NOT NULL DEFAULT '{}'::jsonb. */
      preferences: Record<string, unknown>;
      /** Stripe customer ID. Null until first Stripe interaction. */
      stripeCustomerId: string | null;
      /** Stripe default saved payment method ID. Null until a card is saved. */
      defaultPaymentMethodId: string | null;
      /** Canonical (lowercased) email blind index for dedup lookups. Null until set. */
      emailCanonicalIndex: string | null;
      /** Last 3 digits of phone, plain text. Authoritative source for mh cookie `ph` field. */
      phoneHint: string | null;
    };

    interface Locals {
      session: App.SessionRow | undefined;
      user: App.UserRow | undefined;
      /** True when the authenticated user owns an active (non-frozen, non-banned) vendor account. */
      isVendor: boolean;
      /** True when the authenticated user has an active affiliate_enrollments row. */
      isAffiliate: boolean;
      locale: MultidealLocale;
      /**
       * Set to true when locale was pinned by localePrefixMiddleware before an
       * internal rewrite (e.g. /he/... → /en/...). Prevents the second middleware
       * pass (triggered by context.rewrite) from overwriting the locale.
       */
      localeFixed?: boolean;
      /**
       * Set to true only when localePrefixMiddleware accepted the first path
       * segment as an active locale. Pages under `pages/[locale]/**` must 404
       * without it: the middleware only inspects 2-letter segments, so any
       * longer segment (`/banana/deals`) reaches those pages unvalidated.
       */
      localeValidated?: boolean;
      csrfToken: string | undefined;
      cspNonce: string | undefined;
      services: Services;
      requireUser(): { session: App.SessionRow; user: App.UserRow };
      requireVendorV2(): Promise<{
        session: App.SessionRow;
        user: App.UserRow;
        vendor: VendorRow;
      }>;
      requireAdmin(): { session: App.SessionRow; user: App.UserRow };
      requireCase(caseId: string): Promise<{
        session: App.SessionRow;
        user: App.UserRow;
        case: TransactionCaseRow;
        role: CaseRole;
      }>;
      getCsrfToken(): string;
      dealHit?: DealHit;
      runtime?: {
        env: Env;
        ctx: ExecutionContext;
        cf: CfProperties;
      };
    }
  }
}
