import { Injectable, inject } from '@angular/core';
import { TabService } from '@isa/core/tabs';
import { logger } from '@isa/core/logging';
import {
  NavigationContext,
  NavigationContextData,
  NavigationContextsMetadataSchema,
} from './navigation-context.types';
import { NAVIGATION_CONTEXT_METADATA_KEY } from './navigation-context.constants';

/**
 * Service for managing navigation context using tab metadata storage.
 *
 * This service provides a type-safe approach to preserving navigation state
 * across intermediate navigations, solving the problem of lost router state
 * in multi-step flows.
 *
 * Key Features:
 * - Stores contexts in tab metadata (automatic cleanup when tab closes)
 * - Type-safe with Zod validation
 * - Scoped to individual tabs (no cross-tab pollution)
 * - Simple API with hierarchical scoping support
 *
 * Storage Architecture:
 * - Contexts stored at: `tab.metadata[NAVIGATION_CONTEXT_METADATA_KEY]`
 * - Structure: `{ [customScope]: { data, createdAt } }`
 * - No manual cleanup needed (handled by tab lifecycle)
 *
 * @example
 * ```typescript
 * // Start of flow - preserve context (auto-scoped to active tab)
 * contextService.setContext({
 *   returnUrl: '/original-page',
 *   customerId: 123
 * });
 *
 * // ... intermediate navigations happen ...
 *
 * // End of flow - restore and cleanup
 * const context = contextService.getAndClearContext<{ returnUrl: string }>();
 * if (context?.returnUrl) {
 *   await router.navigateByUrl(context.returnUrl);
 * }
 * ```
 */
@Injectable({ providedIn: 'root' })
export class NavigationContextService {
  readonly #tabService = inject(TabService);
  readonly #log = logger(() => ({ module: 'navigation-context' }));

  /**
   * Get the navigation contexts map from tab metadata.
   *
   * @param tabId The tab ID to get contexts for
   * @returns Record of scope keys to contexts, or empty object if not found
   */
  #getContextsMap(tabId: number): Record<string, NavigationContext> {
    const tab = this.#tabService.entityMap()[tabId];
    if (!tab) {
      this.#log.debug('Tab not found', () => ({ tabId }));
      return {};
    }

    const contextsMap = tab.metadata[NAVIGATION_CONTEXT_METADATA_KEY];
    if (!contextsMap) {
      return {};
    }

    // Validate with Zod schema
    const result = NavigationContextsMetadataSchema.safeParse(contextsMap);
    if (!result.success) {
      this.#log.warn('Invalid contexts map in tab metadata', () => ({
        tabId,
        validationErrors: result.error.errors,
      }));
      return {};
    }

    return result.data as Record<string, NavigationContext>;
  }

  /**
   * Save the navigation contexts map to tab metadata.
   *
   * @param tabId The tab ID to save contexts to
   * @param contextsMap The contexts map to save
   */
  #saveContextsMap(
    tabId: number,
    contextsMap: Record<string, NavigationContext>,
  ): void {
    this.#tabService.patchTabMetadata(tabId, {
      [NAVIGATION_CONTEXT_METADATA_KEY]: contextsMap,
    });
  }

  /**
   * Set a context in the active tab's metadata.
   *
   * Creates or overwrites a navigation context and persists it to tab metadata.
   * The context will automatically be cleaned up when the tab is closed.
   *
   * @template T The type of data being stored in the context
   * @param data The navigation data to preserve
   * @param customScope Optional custom scope (defaults to 'default')
   * @param _ttl Optional TTL parameter (kept for API compatibility but ignored)
   *
   * @example
   * ```typescript
   * // Set context for default scope
   * contextService.setContext({ returnUrl: '/products', selectedIds: [1, 2, 3] });
   *
   * // Set context for custom scope
   * contextService.setContext({ customerId: 42 }, 'customer-details');
   * ```
   */
  async setContext<T extends NavigationContextData>(
    data: T,
    customScope?: string,
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    _ttl?: number, // Kept for API compatibility but ignored
  ): Promise<void> {
    const tabId = this.#tabService.activatedTabId();
    if (tabId === null) {
      throw new Error('No active tab - cannot set navigation context');
    }

    const scopeKey = customScope || 'default';
    const context: NavigationContext = {
      data,
      createdAt: Date.now(),
    };

    const contextsMap = this.#getContextsMap(tabId);
    contextsMap[scopeKey] = context;
    this.#saveContextsMap(tabId, contextsMap);

    this.#log.debug('Context set in tab metadata', () => ({
      tabId,
      scopeKey,
      dataKeys: Object.keys(data),
      totalContexts: Object.keys(contextsMap).length,
    }));
  }

  /**
   * Patch a context in the active tab's metadata.
   *
   * Merges partial data with the existing context, preserving unspecified properties.
   * Properties explicitly set to `undefined` will be removed from the context.
   * If the context doesn't exist, creates a new one (behaves like setContext).
   *
   * @template T The type of data being patched
   * @param partialData The partial navigation data to merge
   * @param customScope Optional custom scope (defaults to 'default')
   *
   * @example
   * ```typescript
   * // Clear one property while preserving others
   * contextService.patchContext({ autoTriggerContinueFn: undefined }, 'select-customer');
   *
   * // Update one property while preserving others
   * contextService.patchContext({ selectedTab: 'details' }, 'customer-flow');
   * ```
   */
  async patchContext<T extends NavigationContextData>(
    partialData: Partial<T>,
    customScope?: string,
  ): Promise<void> {
    const tabId = this.#tabService.activatedTabId();
    if (tabId === null) {
      throw new Error('No active tab - cannot patch navigation context');
    }

    const scopeKey = customScope || 'default';
    const existingContext = await this.getContext<T>(customScope);

    const mergedData = {
      ...(existingContext ?? {}),
      ...partialData,
    };

    // Remove properties explicitly set to undefined
    const removedKeys: string[] = [];
    Object.keys(mergedData).forEach((key) => {
      if (mergedData[key] === undefined) {
        removedKeys.push(key);
        delete mergedData[key];
      }
    });

    const contextsMap = this.#getContextsMap(tabId);
    const context: NavigationContext = {
      data: mergedData,
      createdAt:
        existingContext && contextsMap[scopeKey]
          ? contextsMap[scopeKey].createdAt
          : Date.now(),
    };

    contextsMap[scopeKey] = context;
    this.#saveContextsMap(tabId, contextsMap);

    this.#log.debug('Context patched in tab metadata', () => ({
      tabId,
      scopeKey,
      patchedKeys: Object.keys(partialData),
      removedKeys,
      totalDataKeys: Object.keys(mergedData),
      wasUpdate: existingContext !== null,
      totalContexts: Object.keys(contextsMap).length,
    }));
  }

  /**
   * Get a context from the active tab's metadata without removing it.
   *
   * Retrieves a preserved navigation context by scope.
   *
   * @template T The expected type of the context data
   * @param customScope Optional custom scope (defaults to 'default')
   * @returns The context data, or null if not found
   *
   * @example
   * ```typescript
   * // Get context for default scope
   * const context = contextService.getContext<{ returnUrl: string }>();
   *
   * // Get context for custom scope
   * const context = contextService.getContext<{ customerId: number }>('customer-details');
   * ```
   */
  async getContext<T extends NavigationContextData = NavigationContextData>(
    customScope?: string,
  ): Promise<T | null> {
    const tabId = this.#tabService.activatedTabId();
    if (tabId === null) {
      this.#log.debug('No active tab - cannot get context');
      return null;
    }

    const scopeKey = customScope || 'default';
    const contextsMap = this.#getContextsMap(tabId);
    const context = contextsMap[scopeKey];

    if (!context) {
      this.#log.debug('Context not found', () => ({ tabId, scopeKey }));
      return null;
    }

    this.#log.debug('Context retrieved', () => ({
      tabId,
      scopeKey,
      dataKeys: Object.keys(context.data),
    }));

    return context.data as T;
  }

  /**
   * Get a context from the active tab's metadata and remove it.
   *
   * Retrieves a preserved navigation context and removes it from the metadata.
   * Use this when completing a flow to clean up automatically.
   *
   * @template T The expected type of the context data
   * @param customScope Optional custom scope (defaults to 'default')
   * @returns The context data, or null if not found
   *
   * @example
   * ```typescript
   * // Get and clear context for default scope
   * const context = contextService.getAndClearContext<{ returnUrl: string }>();
   * if (context?.returnUrl) {
   *   await router.navigateByUrl(context.returnUrl);
   * }
   *
   * // Get and clear context for custom scope
   * const context = contextService.getAndClearContext<{ customerId: number }>('customer-details');
   * ```
   */
  async getAndClearContext<
    T extends NavigationContextData = NavigationContextData,
  >(customScope?: string): Promise<T | null> {
    const tabId = this.#tabService.activatedTabId();
    if (tabId === null) {
      this.#log.debug('No active tab - cannot get and clear context');
      return null;
    }

    const scopeKey = customScope || 'default';
    const contextsMap = this.#getContextsMap(tabId);
    const context = contextsMap[scopeKey];

    if (!context) {
      this.#log.debug('Context not found for clearing', () => ({
        tabId,
        scopeKey,
      }));
      return null;
    }

    // Remove from map
    delete contextsMap[scopeKey];
    this.#saveContextsMap(tabId, contextsMap);

    this.#log.debug('Context retrieved and cleared', () => ({
      tabId,
      scopeKey,
      dataKeys: Object.keys(context.data),
      remainingContexts: Object.keys(contextsMap).length,
    }));

    return context.data as T;
  }

  /**
   * Clear a specific context from the active tab's metadata.
   *
   * Removes a context without returning its data.
   * Useful for explicit cleanup without needing the data.
   *
   * @param customScope Optional custom scope (defaults to 'default')
   * @returns true if context was found and cleared, false otherwise
   *
   * @example
   * ```typescript
   * // Clear context for default scope
   * contextService.clearContext();
   *
   * // Clear context for custom scope
   * contextService.clearContext('customer-details');
   * ```
   */
  async clearContext(customScope?: string): Promise<boolean> {
    const result = await this.getAndClearContext(customScope);
    return result !== null;
  }

  /**
   * Clear all contexts for the active tab.
   *
   * Removes all contexts from the active tab's metadata.
   * Useful for cleanup when a workflow is cancelled or completed.
   *
   * @returns The number of contexts cleared
   *
   * @example
   * ```typescript
   * // Clear all contexts for active tab
   * const cleared = contextService.clearScope();
   * console.log(`Cleared ${cleared} contexts`);
   * ```
   */
  async clearScope(): Promise<number> {
    const tabId = this.#tabService.activatedTabId();
    if (tabId === null) {
      this.#log.warn('Cannot clear scope: no active tab');
      return 0;
    }

    const contextsMap = this.#getContextsMap(tabId);
    const contextCount = Object.keys(contextsMap).length;

    if (contextCount === 0) {
      return 0;
    }

    // Clear entire metadata key
    this.#tabService.patchTabMetadata(tabId, {
      [NAVIGATION_CONTEXT_METADATA_KEY]: {},
    });

    this.#log.debug('Tab scope cleared', () => ({
      tabId,
      clearedCount: contextCount,
    }));

    return contextCount;
  }

  /**
   * Clear all contexts from the active tab (alias for clearScope).
   *
   * This method is kept for backward compatibility with the previous API.
   * It clears all contexts for the active tab only, not globally.
   *
   * @example
   * ```typescript
   * contextService.clearAll();
   * ```
   */
  async clearAll(): Promise<void> {
    await this.clearScope();
    this.#log.debug('All contexts cleared for active tab');
  }

  /**
   * Check if a context exists for the active tab.
   *
   * @param customScope Optional custom scope (defaults to 'default')
   * @returns true if context exists, false otherwise
   *
   * @example
   * ```typescript
   * // Check default scope
   * if (contextService.hasContext()) {
   *   const context = contextService.getContext();
   * }
   *
   * // Check custom scope
   * if (contextService.hasContext('customer-details')) {
   *   const context = contextService.getContext('customer-details');
   * }
   * ```
   */
  async hasContext(customScope?: string): Promise<boolean> {
    const context = await this.getContext(customScope);
    return context !== null;
  }

  /**
   * Get the current context count for the active tab (for debugging/monitoring).
   *
   * @returns The total number of contexts in the active tab's metadata
   *
   * @example
   * ```typescript
   * const count = await contextService.getContextCount();
   * console.log(`Active tab has ${count} contexts`);
   * ```
   */
  async getContextCount(): Promise<number> {
    const tabId = this.#tabService.activatedTabId();
    if (tabId === null) {
      return 0;
    }

    const contextsMap = this.#getContextsMap(tabId);
    return Object.keys(contextsMap).length;
  }
}
