
/**
 * Identity service for managing persistent visitor and session identifiers using cookies.
 * Handles consent-based activation, automatic session renewal via user activity, and provides
 * IDs to analytics tracking modules when user consent is granted.
 */

export interface IdentityState {
  visitorId: string;
  sessionId: string;
}

const VISITOR_COOKIE = 'adalong_vid';
const SESSION_COOKIE = 'adalong_sid';
const VISITOR_MAX_AGE = 60 * 60 * 24 * 365; // 365 days
const SESSION_MAX_AGE = 60 * 30; // 30 minutes

// Reference count so multiple consumers (analytics modules) can enable/disable without racing.
let usageCount = 0;
let visitorId: string | null = null;
let sessionId: string | null = null;
let activityAttached = false;

const activityEvents: Array<keyof WindowEventMap> = ['click', 'touchstart', 'keydown'];

const readCookie = (name: string): string | null => {
  if (typeof document === 'undefined') {
    return null;
  }

  const cookies = document.cookie ? document.cookie.split('; ') : [];
  for (let i = 0; i < cookies.length; i += 1) {
    const [cookieName, ...valueParts] = cookies[i].split('=');
    if (cookieName === name) {
      return decodeURIComponent(valueParts.join('='));
    }
  }
  return null;
};

const writeCookie = (name: string, value: string, maxAge: number): void => {
  if (typeof document === 'undefined') {
    return;
  }

  const encodedValue = encodeURIComponent(value);
  const expires = new Date(Date.now() + maxAge * 1000).toUTCString();
  const secure = typeof window !== 'undefined' && window.location.protocol === 'https:' ? '; Secure' : '';
  document.cookie = `${name}=${encodedValue}; Max-Age=${maxAge}; Expires=${expires}; Path=/; SameSite=Lax${secure}`;
};

const deleteCookie = (name: string): void => {
  if (typeof document === 'undefined') {
    return;
  }

  const secure = typeof window !== 'undefined' && window.location.protocol === 'https:' ? '; Secure' : '';
  document.cookie = `${name}=; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Path=/; SameSite=Lax${secure}`;
};

const randomId = (prefix: 'vid' | 'sid'): string => {
  if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
    return `${prefix}_${crypto.randomUUID().replace(/-/g, '')}`;
  }
  const random = Math.random().toString(36).slice(2, 12);
  const timestamp = Date.now().toString(36);
  return `${prefix}_${timestamp}${random}`;
};

const ensureVisitor = (): void => {
  if (visitorId) {
    writeCookie(VISITOR_COOKIE, visitorId, VISITOR_MAX_AGE);
    return;
  }

  const existing = readCookie(VISITOR_COOKIE);
  visitorId = existing && existing.startsWith('vid_') ? existing : randomId('vid');
  writeCookie(VISITOR_COOKIE, visitorId, VISITOR_MAX_AGE);
};

const ensureSession = (): void => {
  if (sessionId) {
    writeCookie(SESSION_COOKIE, sessionId, SESSION_MAX_AGE);
    return;
  }

  const existing = readCookie(SESSION_COOKIE);
  sessionId = existing && existing.startsWith('sid_') ? existing : randomId('sid');
  writeCookie(SESSION_COOKIE, sessionId, SESSION_MAX_AGE);
};

const handleActivity = (): void => {
  if (!isActive()) {
    return;
  }
  ensureSession();
};

/**
 * Attaches activity listeners so user interactions automatically renew the session.
 * These listeners keep the short-lived session alive as long as the user remains
 * active (clicks, touches, key presses) and avoids forcing re-auth when analytics
 * tracking is still in use.
 */
const attachActivityListeners = (): void => {
  if (activityAttached || typeof window === 'undefined') {
    return;
  }

  activityEvents.forEach((eventName) => {
    window.addEventListener(eventName, handleActivity, { passive: true });
  });

  activityAttached = true;
};

const detachActivityListeners = (): void => {
  if (!activityAttached || typeof window === 'undefined') {
    return;
  }

  activityEvents.forEach((eventName) => {
    window.removeEventListener(eventName, handleActivity);
  });

  activityAttached = false;
};

const isActive = (): boolean => usageCount > 0;

/**
 * Called when analytics consent is granted. Tracks how many consumers are active
 * so shared resources (cookies/listeners) are only created once.
 */
const enable = (): void => {
  usageCount += 1;
  if (usageCount > 1) {
    return;
  }

  ensureVisitor();
  ensureSession();
  attachActivityListeners();
};

/**
 * Called when consumers drop consent/stop tracking. Releases listeners once nobody
 * is using the service by waiting for usageCount to reach zero.
 */
const disable = (): void => {
  usageCount = Math.max(usageCount - 1, 0);
  if (usageCount === 0) {
    detachActivityListeners();
  }
};

const clear = (): void => {
  usageCount = 0;
  detachActivityListeners();
  deleteCookie(VISITOR_COOKIE);
  deleteCookie(SESSION_COOKIE);
  visitorId = null;
  sessionId = null;
};

/**
 * Returns the current visitor/session IDs. Analytics pipelines call this when
 * consent is present to include the IDs in server calls, and also `touchSession`
 * is invoked to keep the session alive while users are active.
 */
const getIdentity = (): IdentityState | null => {
  if (isActive()) {
    ensureVisitor();
    ensureSession();
  } else {
    const vid = readCookie(VISITOR_COOKIE);
    const sid = readCookie(SESSION_COOKIE);
    visitorId = vid && vid.startsWith('vid_') ? vid : visitorId;
    sessionId = sid && sid.startsWith('sid_') ? sid : sessionId;
  }

  if (!visitorId || !sessionId) {
    return null;
  }

  return { visitorId, sessionId };
};

const touchSession = (): void => {
  if (!isActive()) {
    return;
  }
  ensureSession();
};

export const Identity = {
  enable,
  disable,
  getIdentity,
  touchSession,
  clear,
};
