import { assert, assertString } from './util/mod.ts';


/** Used to determine server settings to use. */
type Touchpoint = 'desktop' | 'mobile';

/** Session is used to handle session information for a specific visitor. */
interface Session {
  /**
   * Method for retrieving session information, synchronously
   * or asynchronously. Returns a `SessionMetadata` object that
   * contains `customerKey` and `sessionKey`.
   *
   * @example
   * ```ts
   * declare const session: Session;
   *
   * const { customerKey, sessionKey } = await session();
   * ```
   */
  (): SessionMetadata | Promise<SessionMetadata>;
}

/** Metadata information regarding a visitor, used during requests to the Elevate Storefront API. */
interface SessionMetadata {
  /**
   * A key that uniquely identifies the current visitor. Using UUIDs as keys are recommended.
   * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/common/query-parameters/#mandatory-parameters
   */
  customerKey: string;
  /**
   * A unique key, identifying the session. Using UUIDs as keys are recommended.
   * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/common/query-parameters/#mandatory-parameters
   */
  sessionKey: string;
}

/**
 * Configuration required during initialization for the Elevate Storefront API.
 *
 * @example
 * ```ts
 * const config: Config = { ... };
 * const api = elevate(config);
 * ```
 */
interface Config {
  /**
   * The ID for the Elevate cluster for all subsequent API requests.
   * Production/Test/Development clusters each have their own unique ID.
   * The value for `clusterId` can be found in credentials tab in
   * the Voyado Elevate app,
   *
   * The value should be:
   * - An identifier in a format similar to `'w00000000'`
   * - A URL with the origin to the Elevate cluster, e.g.
   *   `'https://w00000000.api.esales.apptus.cloud/'`
   *
   * @example
   * ```ts
   * const api = elevate({ clusterId: 'w00000000' });
   * ```
   */
  clusterId: string;
  /** Required for loading correct products and behavior data. */
  market: string;
  /** Required for loading localized product and facet data. */
  locale: string;
  /** Used to determine various server settings. */
  touchpoint: Touchpoint;
  /** Configures how session metadata (customerKey & sessionKey) is retrieved before requests to the API */
  session: Session;
}

/**
 * Subset of Config for interacting with the Notifications API.
 * @see Config
 */
type NotificationConfig = Pick<Config, 'clusterId' | 'market' | 'session'>;

/**
 * Created from a validated `Config`. Used internally.
 * @internal
 */
interface BaseOptions extends Omit<NotificationConfig, 'clusterId'> {
  clusterUrl: string;
}

/**
 * Created from a validated `Config`. Used internally.
 * @internal
 */
interface FullOptions extends Omit<Config, 'clusterId'> {
  clusterUrl: string;
}

function validateBaseConfig(config: unknown): asserts config is NotificationConfig {
  assert(typeof config === 'object' && config !== null, 'API config must be an "object"');

  const c: Partial<NotificationConfig> = config;

  assertString(c.clusterId, 'Property "clusterId" must be a non-empty string');
  assertString(c.market, 'Property "market" must be a non-empty string');
  assert(typeof c.session === 'function', 'Property "session" must be a method.');
}

/**
 * Validates that user supplied config is in a correct format. `unknown` is used as parameter
 * value to indicate that we do not yet trust the user input.
 *
 * @param config raw configuration for creating the API library to be validated
 */
export function validateConfig(config: unknown): asserts config is Config {
  validateBaseConfig(config);

  const c: Partial<Config> = config;
  const touchpoints: Touchpoint[] = ['desktop', 'mobile'];

  assertString(c.locale, 'Property "locale" must be a string and valid locale, e.g. "en-US".');
  assert(touchpoints.includes(c.touchpoint!), `Property "touchpoint" must be one of: ${touchpoints.join(', ')}`);
}

function resolveClusterUrl<T extends NotificationConfig>(config: T) {
  const { clusterId, ...rest } = config;

  const clusterUrl = (() => {
    /** Ensures consistent casing and trailing slash of URL's */
    const normalizeUrl = (url: string) => new URL(url).href;

    try {
      // Will throw for invalid URL, and normalize trailing slash
      return normalizeUrl(clusterId);
    } catch { /* empty */ }
    return normalizeUrl(`https://${clusterId}.api.esales.apptus.cloud/`);
  })();

  return { ...rest, clusterUrl };
}

/** Prepares user-supplied configuration for internal use. */
export function resolveConfig(config: Config): FullOptions {
  // TODO(csv): Allow this code to be removed in a production build?
  validateConfig(config);
  return resolveClusterUrl(config);
}

/** Prepares user-supplied configuration for internal use. */
export function resolveNotificationConfig(config: NotificationConfig): BaseOptions {
  // TODO(csv): Allow this code to be removed in a production build?
  validateBaseConfig(config);
  return resolveClusterUrl(config);
}

export type { Config, NotificationConfig, BaseOptions, FullOptions, Session, SessionMetadata, Touchpoint };
