export const DEFAULT_SESSION_VERSION = 2;
export const SESSION_STORAGE_KEY = createSessionStorageKey(
  DEFAULT_SESSION_VERSION,
);
const LEGACY_SESSION_STORAGE_KEYS = [
  "akintu:conversation:v1:session-id",
] as const;

export interface SessionCrypto {
  getRandomValues?: Crypto["getRandomValues"];
  randomUUID?: Crypto["randomUUID"];
}

export interface SessionIdOptions {
  storage?: Pick<Storage, "getItem" | "setItem"> &
    Partial<Pick<Storage, "removeItem">>;
  crypto?: SessionCrypto;
  sessionVersion?: number | undefined;
}

/**
 * Restores conversation continuity using the only value the widget persists.
 * The storage key is versioned and deliberately independent from the API key
 * so inspecting storage cannot reveal tenant credentials.
 */
export function getOrCreateSessionId(options: SessionIdOptions = {}): string {
  const storage = options.storage ?? getBrowserStorage();
  const storageKey = createSessionStorageKey(
    options.sessionVersion ?? DEFAULT_SESSION_VERSION,
  );
  const storedSessionId = getStoredSessionId(storage, storageKey);
  if (storedSessionId !== undefined) return storedSessionId;

  const sessionId = createSessionId(options.crypto ?? globalThis.crypto);
  try {
    storage?.setItem(storageKey, sessionId);
    removeLegacySessions(storage, storageKey);
  } catch {
    // Storage can be unavailable in privacy modes; the in-memory session still works.
  }
  return sessionId;
}

/** Starts a clean conversation while retaining previous server-side analytics. */
export function resetConversationSession(
  options: SessionIdOptions = {},
): string {
  const storage = options.storage ?? getBrowserStorage();
  const storageKey = createSessionStorageKey(
    options.sessionVersion ?? DEFAULT_SESSION_VERSION,
  );
  const sessionId = createSessionId(options.crypto ?? globalThis.crypto);
  try {
    storage?.setItem(storageKey, sessionId);
    removeLegacySessions(storage, storageKey);
  } catch {
    // Reloading still starts an in-memory session when browser storage is blocked.
  }
  return sessionId;
}

function createSessionId(cryptoSource: SessionCrypto | undefined): string {
  // randomUUID is unavailable in some older or non-secure browser contexts;
  // getRandomValues preserves UUID-quality entropy without blocking the widget.
  if (cryptoSource?.randomUUID !== undefined) {
    return cryptoSource.randomUUID();
  }
  if (cryptoSource?.getRandomValues !== undefined) {
    const bytes = cryptoSource.getRandomValues(new Uint8Array(16));
    bytes[6] = ((bytes[6] ?? 0) & 0x0f) | 0x40;
    bytes[8] = ((bytes[8] ?? 0) & 0x3f) | 0x80;
    const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0"));
    return [
      hex.slice(0, 4).join(""),
      hex.slice(4, 6).join(""),
      hex.slice(6, 8).join(""),
      hex.slice(8, 10).join(""),
      hex.slice(10).join(""),
    ].join("-");
  }

  // This identifier is not an authentication secret. The last fallback keeps
  // conversation requests usable when Web Crypto itself is unavailable.
  return `akintu-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
}

function getStoredSessionId(
  storage:
    | (Pick<Storage, "getItem" | "setItem"> &
        Partial<Pick<Storage, "removeItem">>)
    | undefined,
  storageKey: string,
): string | undefined {
  try {
    const value = storage?.getItem(storageKey)?.trim();
    if (value !== undefined && value.length >= 1 && value.length <= 128) {
      return value;
    }
  } catch {
    // A blocked storage backend must not prevent the widget from starting.
  }
  return undefined;
}

function createSessionStorageKey(sessionVersion: number): string {
  const normalizedVersion =
    Number.isInteger(sessionVersion) && sessionVersion > 0
      ? sessionVersion
      : DEFAULT_SESSION_VERSION;
  return `akintu:conversation:v${normalizedVersion}:session-id`;
}

function removeLegacySessions(
  storage: Partial<Pick<Storage, "removeItem">> | undefined,
  activeStorageKey: string,
): void {
  for (const storageKey of LEGACY_SESSION_STORAGE_KEYS) {
    if (storageKey !== activeStorageKey) storage?.removeItem?.(storageKey);
  }
}

function getBrowserStorage(): Storage | undefined {
  try {
    return typeof window === "undefined" ? undefined : window.localStorage;
  } catch {
    return undefined;
  }
}
