// src/client/shared/api/BaseRepository.ts
// Base Repository with Memory Cache for all API operations

import type { z } from 'zod';
import { api, type RequestConfig } from './api.client';

/**
 * Конфигурация кэша
 */
interface CacheEntry<T> {
  data: T;
  timestamp: number;
  expiresAt: number;
}

/**
 * Конфигурация репозитория
 */
export interface RepositoryConfig {
  /**
   * Время жизни кэша в миллисекундах (по умолчанию 30000мс = 30сек)
   */
  cacheTtl?: number;
}

/**
 * Базовый класс репозитория с кэшированием
 */
export abstract class BaseRepository {
  protected cache: Map<string, CacheEntry<any>> = new Map();
  protected cacheTtl: number;
  protected abstract basePath: string;

  constructor(config: RepositoryConfig = {}) {
    this.cacheTtl = config.cacheTtl ?? 30000; // 30 секунд по умолчанию
  }

  /**
   * Генерирует ключ кэша на основе endpoint и параметров
   */
  protected getCacheKey(endpoint: string, params?: Record<string, any>): string {
    const paramsStr = params ? JSON.stringify(params) : '';
    return `${endpoint}:${paramsStr}`;
  }

  /**
   * Получает данные из кэша, если они не истекли
   */
  protected getFromCache<T>(key: string): T | null {
    const entry = this.cache.get(key);
    if (!entry) {
      return null;
    }

    const now = Date.now();
    if (now > entry.expiresAt) {
      // Кэш истек
      this.cache.delete(key);
      return null;
    }

    return entry.data as T;
  }

  /**
   * Сохраняет данные в кэш
   */
  protected setCache<T>(key: string, data: T): void {
    const now = Date.now();
    this.cache.set(key, {
      data,
      timestamp: now,
      expiresAt: now + this.cacheTtl,
    });
  }

  /**
   * Очищает кэш для конкретного ключа
   */
  protected clearCacheKey(key: string): void {
    this.cache.delete(key);
  }

  /**
   * Очищает весь кэш репозитория
   */
  protected clearAllCache(): void {
    this.cache.clear();
  }

  /**
   * Выполняет GET запрос с кэшированием
   * @param endpoint - endpoint (относительный путь)
   * @param config - конфигурация запроса с опциональной Zod-схемой
   */
  protected async get<ResponseData = any>(
    endpoint: string,
    config?: RequestConfig<ResponseData>
  ): Promise<ResponseData> {
    const fullEndpoint = `${this.basePath}${endpoint}`;
    const cacheKey = this.getCacheKey(fullEndpoint, config?.params);

    // Проверяем кэш
    const cached = this.getFromCache<ResponseData>(cacheKey);
    if (cached !== null) {
      return cached;
    }

    // Делаем запрос
    const response = await api.get<ResponseData>(fullEndpoint, config);

    // Сохраняем в кэш
    this.setCache(cacheKey, response);

    return response;
  }

  /**
   * Выполняет GET запрос без кэширования
   * @param endpoint - endpoint (относительный путь)
   * @param config - конфигурация запроса с опциональной Zod-схемой
   */
  protected async getNoCache<ResponseData = any>(
    endpoint: string,
    config?: RequestConfig<ResponseData>
  ): Promise<ResponseData> {
    const fullEndpoint = `${this.basePath}${endpoint}`;
    return api.get<ResponseData>(fullEndpoint, config);
  }

  /**
   * Выполняет POST запрос
   * @param endpoint - endpoint (относительный путь)
   * @param body - тело запроса
   * @param config - конфигурация запроса с опциональной Zod-схемой
   */
  protected async post<RequestData = any, ResponseData = any>(
    endpoint: string,
    body: RequestData,
    config?: RequestConfig<ResponseData>
  ): Promise<ResponseData> {
    const fullEndpoint = `${this.basePath}${endpoint}`;
    return api.post<RequestData, ResponseData>(fullEndpoint, body, config);
  }

  /**
   * Выполняет PUT запрос
   * @param endpoint - endpoint (относительный путь)
   * @param body - тело запроса
   * @param config - конфигурация запроса с опциональной Zod-схемой
   */
  protected async put<RequestData = any, ResponseData = any>(
    endpoint: string,
    body: RequestData,
    config?: RequestConfig<ResponseData>
  ): Promise<ResponseData> {
    const fullEndpoint = `${this.basePath}${endpoint}`;
    return api.put<RequestData, ResponseData>(fullEndpoint, body, config);
  }

  /**
   * Выполняет PATCH запрос
   * @param endpoint - endpoint (относительный путь)
   * @param body - тело запроса
   * @param config - конфигурация запроса с опциональной Zod-схемой
   */
  protected async patch<RequestData = any, ResponseData = any>(
    endpoint: string,
    body: RequestData,
    config?: RequestConfig<ResponseData>
  ): Promise<ResponseData> {
    const fullEndpoint = `${this.basePath}${endpoint}`;
    return api.patch<RequestData, ResponseData>(fullEndpoint, body, config);
  }

  /**
   * Выполняет DELETE запрос
   * @param endpoint - endpoint (относительный путь)
   * @param config - конфигурация запроса с опциональной Zod-схемой
   */
  protected async delete<ResponseData = any>(
    endpoint: string,
    config?: RequestConfig<ResponseData>
  ): Promise<ResponseData> {
    const fullEndpoint = `${this.basePath}${endpoint}`;
    return api.delete<ResponseData>(fullEndpoint, config);
  }

  /**
    * Инвалидирует кэш для всех GET запросов
    * Полезно после операций POST/PUT/PATCH/DELETE
    */
  protected invalidateCache(): void {
    this.clearAllCache();
  }

  /**
   * Smart Merge: обновляет только поля, пришедшие с сервера
   * Сохраняет локальные поля фронтенда (например, isDragging, isEditing)
   * @param oldData - старые данные (текущее состояние)
   * @param newData - новые данные (с сервера)
   * @returns объединенные данные
   */
  protected patchData<T extends Record<string, any>>(oldData: T, newData: Partial<T>): T {
    const result: any = { ...oldData };
    
    // Обновляем только поля, которые есть в newData
    for (const key in newData) {
      if (newData[key] !== undefined) {
        result[key] = newData[key];
      }
    }
    
    return result;
  }
}

/**
 * Фабрика для создания репозиториев
 */
export function createRepository<T extends BaseRepository>(
  RepositoryClass: new (config?: RepositoryConfig) => T,
  config?: RepositoryConfig
): T {
  return new RepositoryClass(config);
}
