import {
  APP_INITIALIZER,
  EnvironmentProviders,
  InjectionToken,
  Provider,
  Type,
  makeEnvironmentProviders,
} from '@angular/core';
import { LogLevel } from './log-level.enum';
import { LoggingService } from './logging.service';
import { LoggerContext, LoggingConfig, Sink, SinkFn } from './logging.types';

/**
 * Injection token for the logger configuration.
 * Used internally by the logging service to configure its behavior.
 * @internal
 */
export const LOGGER_CONFIG = new InjectionToken<LoggingConfig>('LOGGER_CONFIG');

/**
 * Injection token for hierarchical logger context.
 * Allows components and services to provide context that will be included
 * in all log messages within their scope.
 * @internal
 */
export const LOGGER_CONTEXT = new InjectionToken<LoggerContext[]>(
  'LOGGER_CONTEXT',
);

/**
 * Configuration data interface for the logging system.
 * Used internally to represent partial configuration updates.
 * @internal
 */
interface LoggingConfigData {
  level?: LogLevel;
  sinks?: (Sink | SinkFn | Type<Sink>)[];
  context?: LoggerContext;
}

/**
 * Configures the LoggingService during application initialization.
 * @internal
 */
export function configureLogger(
  loggingService: LoggingService,
  config: LoggingConfig,
): () => void {
  return () => {
    loggingService.configure(config);
  };
}

/**
 * Sets the minimum log level for the logging system.
 * Messages below this level will be ignored.
 *
 * @param level - The minimum log level to process
 * @returns Configuration object for the logging system
 *
 * @example
 * ```typescript
 * import { isDevMode } from '@angular/core';
 * import { provideLogging, withLogLevel, LogLevel } from '@isa/core/logging';
 *
 * // In app.config.ts
 * export const appConfig: ApplicationConfig = {
 *   providers: [
 *     provideLogging(
 *       withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn)
 *     )
 *   ]
 * };
 * ```
 */
export function withLogLevel(level: LogLevel): LoggingConfigData {
  return { level };
}

/**
 * Adds a logging sink (destination) to the configuration.
 * A sink can be provided as an instance, a class reference, or a factory function.
 *
 * @param sink - The sink to add (instance or class reference)
 * @returns Configuration object for the logging system
 *
 * @example
 * ```typescript
 * import { provideLogging, withSink, ConsoleLogSink } from '@isa/core/logging';
 *
 * // In app.config.ts
 * export const appConfig: ApplicationConfig = {
 *   providers: [
 *     provideLogging(
 *       withSink(ConsoleLogSink), // Using class reference
 *       withSink(new CustomLogSink()) // Using instance
 *     )
 *   ]
 * };
 * ```
 */
export function withSink(sink: Sink | Type<Sink>): LoggingConfigData {
  return { sinks: [sink] };
}

/**
 * Adds a sink function to the logging configuration.
 * Sink functions are useful when the sink needs access to injected dependencies.
 *
 * @param sinkFn - Factory function that creates the sink
 * @returns Configuration object for the logging system
 *
 * @example
 * ```typescript
 * import { provideLogging, withSinkFn } from '@isa/core/logging';
 *
 * const remoteSink: SinkFn = () => {
 *   const http = inject(HttpClient);
 *   return (level, message, context) => {
 *     http.post('/logs', { level, message, context }).subscribe();
 *   };
 * };
 *
 * // In app.config.ts
 * export const appConfig: ApplicationConfig = {
 *   providers: [
 *     provideLogging(
 *       withSinkFn(remoteSink)
 *     )
 *   ]
 * };
 * ```
 */
export function withSinkFn(sinkFn: SinkFn): LoggingConfigData {
  return { sinks: [sinkFn] };
}

/**
 * Adds global context to the logger configuration.
 * This context will be included in all log messages.
 *
 * @param context - The context object to add to all log messages
 * @returns Configuration object for the logging system
 *
 * @example
 * ```typescript
 * import { provideLogging, withContext } from '@isa/core/logging';
 *
 * // In app.config.ts
 * export const appConfig: ApplicationConfig = {
 *   providers: [
 *     provideLogging(
 *       withContext({
 *         version: '1.0.0',
 *         environment: 'production'
 *       })
 *     )
 *   ]
 * };
 * ```
 */
export function withContext(context: LoggerContext): LoggingConfigData {
  return { context };
}

/**
 * Provides logging functionality for the application.
 * This is the main configuration entry point for the logging system.
 *
 * Features:
 * - Configure minimum log level
 * - Add multiple logging destinations (sinks)
 * - Set global context for all log messages
 * - Flexible configuration through builder functions
 *
 * @param configs - Array of configuration objects created by builder functions
 * @returns Environment providers for the logging system
 *
 * @example
 * ```typescript
 * import { ApplicationConfig, isDevMode } from '@angular/core';
 * import {
 *   provideLogging,
 *   withLogLevel,
 *   withSink,
 *   withContext,
 *   LogLevel,
 *   ConsoleLogSink
 * } from '@isa/core/logging';
 *
 * // In app.config.ts
 * export const appConfig: ApplicationConfig = {
 *   providers: [
 *     provideLogging(
 *       withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn),
 *       withSink(ConsoleLogSink),
 *       withContext({ app: 'MyApp', version: '1.0.0' })
 *     )
 *   ]
 * };
 * ```
 */
export function provideLogging(
  ...configs: LoggingConfigData[]
): EnvironmentProviders {
  // Merge all configurations
  const mergedConfig: LoggingConfig = {
    level: LogLevel.Info, // Default level
    sinks: [],
  };

  for (const config of configs) {
    if (config.level !== undefined) {
      mergedConfig.level = config.level;
    }

    if (config.sinks) {
      mergedConfig.sinks.push(...config.sinks);
    }

    if (config.context) {
      mergedConfig.context = {
        ...mergedConfig.context,
        ...config.context,
      };
    }
  }

  return makeEnvironmentProviders([
    {
      provide: LOGGER_CONFIG,
      useValue: mergedConfig,
    },
    {
      provide: APP_INITIALIZER,
      useFactory: configureLogger,
      deps: [LoggingService, LOGGER_CONFIG],
      multi: true,
    },
  ]);
}

/**
 * Provides a context object for logging within a specific component or module scope.
 * This context will be merged with other contexts in the hierarchy when logging.
 *
 * @param context - The context object to provide for this scope
 * @returns Provider array for the context
 *
 * @example
 * ```typescript
 * import { Component } from '@angular/core';
 * import { logger, provideLoggerContext } from '@isa/core/logging';
 *
 * @Component({
 *   selector: 'app-user-profile',
 *   providers: [
 *     provideLoggerContext({
 *       component: 'UserProfile',
 *       section: 'Account'
 *     })
 *   ]
 * })
 * export class UserProfileComponent {
 *   #logger = logger();
 *
 *   ngOnInit(): void {
 *     // Log will include the component context automatically
 *     this.#logger.info('Component initialized');
 *   }
 * }
 * ```
 */
export function provideLoggerContext(context: LoggerContext): Provider[] {
  return [
    {
      provide: LOGGER_CONTEXT,
      useValue: context,
      multi: true,
    },
  ];
}
