import { inject } from '@angular/core';
import { LoggingService } from './logging.service';
import {
  LoggerApi,
  LoggerContext,
  MaybeLoggerContextFn,
} from './logging.types';
import { LOGGER_CONTEXT } from './logging.providers';

/**
 * Factory function to create a logger instance with rich context support.
 * This is the primary way for components and services to access logging functionality.
 *
 * The logger supports hierarchical context through multiple mechanisms:
 * 1. Component-level context via provideLoggerContext()
 * 2. Instance-level context via the ctxFn parameter
 * 3. Message-level context via the logging methods
 *
 * These contexts are merged in order, with later contexts taking precedence.
 *
 * @param ctxFn - Optional function that returns dynamic context to be included with each log message
 * @returns A LoggerApi instance configured with the specified context
 *
 * @example
 * ```typescript
 * // Basic usage
 * const logger = logger();
 * logger.info('Simple message');
 *
 * // With dynamic context
 * const loggerWithContext = logger(() => ({
 *   userId: getCurrentUserId(),
 *   sessionId: getSessionId()
 * }));
 *
 * // Usage with component-level context
 * @Component({
 *   providers: [
 *     provideLoggerContext({ component: 'UserProfile' })
 *   ]
 * })
 * class UserProfileComponent {
 *   #logger = logger(() => ({ userId: this.currentUserId }));
 *
 *   logAction(action: string) {
 *     this.#logger.info('User action', { action }); // Includes all context levels
 *   }
 * }
 * ```
 */
export function logger(ctx?: MaybeLoggerContextFn): LoggerApi {
  const loggingService = inject(LoggingService);

  // Try to inject context if available
  const context =
    inject(LOGGER_CONTEXT, {
      optional: true,
    }) || [];

  // Return an object with methods that forward to the logging service
  // with the provided context
  return {
    /**
     * Logs trace information with optional additional context.
     * @param message The log message.
     * @param additionalContext Optional context data specific to this log message.
     */
    trace: (
      message: string,
      additionalContext?: MaybeLoggerContextFn,
    ): void => {
      loggingService.trace(message, () =>
        mergeContexts(context, ctx, additionalContext),
      );
    },
    /**
     * Logs debug information with optional additional context.
     * @param message The log message.
     * @param additionalContext Optional context data specific to this log message.
     */
    debug: (message: string, additionalContext?: () => LoggerContext): void => {
      loggingService.debug(message, () =>
        mergeContexts(context, ctx, additionalContext),
      );
    },
    /**
     * Logs informational messages with optional additional context.
     * @param message The log message.
     * @param additionalContext Optional context data specific to this log message.
     */
    info: (message: string, additionalContext?: () => LoggerContext): void => {
      loggingService.info(message, () =>
        mergeContexts(context, ctx, additionalContext),
      );
    },
    /**
     * Logs warning messages with optional additional context.
     * @param message The log message.
     * @param additionalContext Optional context data specific to this log message.
     */
    warn: (message: string, additionalContext?: () => LoggerContext): void => {
      loggingService.warn(message, () =>
        mergeContexts(context, ctx, additionalContext),
      );
    },
    /**
     * Logs error messages with optional error object and additional context.
     * @param message The log message.
     * @param error Optional error object.
     * @param additionalContext Optional context data specific to this log message.
     */
    error: (
      message: string,
      error: Error,
      additionalContext?: () => LoggerContext,
    ): void => {
      loggingService.error(message, error, () =>
        mergeContexts(context, ctx, additionalContext),
      );
    },
  };
}

/**
 * Merges multiple levels of context into a single object.
 * Handles context priority and merging based on the logging system's hierarchy:
 * Component context -> Instance context -> Message context
 *
 * @param baseContext - Array of component-level contexts from provideLoggerContext
 * @param injectorContext - Optional function returning instance-level context
 * @param additionalContext - Message-specific context provided in the log call
 * @returns A merged context object containing properties from all provided contexts
 *
 * @internal
 */
function mergeContexts(
  baseContext: LoggerContext[] | null,
  injectorContext?: MaybeLoggerContextFn,
  additionalContext?: MaybeLoggerContextFn,
): LoggerContext {
  const contextArray = Array.isArray(baseContext) ? baseContext : [];

  const injectorCtx = injectorContext
    ? typeof injectorContext === 'function'
      ? injectorContext()
      : injectorContext
    : {};

  if (injectorCtx && typeof injectorCtx === 'object') {
    contextArray.push(injectorCtx);
  }

  const additionalCtx = additionalContext
    ? typeof additionalContext === 'function'
      ? additionalContext()
      : additionalContext
    : {};

  if (additionalCtx && typeof additionalCtx === 'object') {
    contextArray.push(additionalCtx);
  }

  if (!contextArray.length) {
    return {};
  }

  return contextArray.reduce((acc, context) => ({ ...acc, ...context }), {});
}
