import { DomaApplication, CreateApplicationPayload, Outage, ApplicationComment, ApplicationHistoryEntry, District, Building, BuildingInventoryItem, PersonalAccount, Employee, Position, LegalDebtor, PreTrialWork, PreTrialAction, PromisedPayment, LegalContract, LegalCourtCase, DevPipelineItem, DevOSSSession, DevAuditData, DevMarketingActivity, PREvent, PREventPhoto, SMMChannel, SMMSubscriberSnapshot, SMMChannelsSummary, AttractionAction, PostTopic, ScheduledPost, User, UserPreferences, UserRole, CompanyNews, Department } from '../types';

export interface PortalUserRow {
  id: number;
  employeeId: string;
  login: string;
  email: string | null;
  role: UserRole;
  permissions?: string[] | null;
  scope?: 'all' | 'own_district';
  createdAt?: string;
  employeeName: string;
  employeePosition: string;
}

export interface PermissionTemplateRow {
  id: number;
  name: string;
  description: string | null;
  permissions: string[];
  scope?: 'all' | 'own_district';
  forPosition?: string | null;
  suggestedRole?: UserRole | null;
  createdAt?: string;
  updatedAt?: string;
}
import { connectionService } from './connectionService';
import { offlineCacheService } from './offlineCacheService';
import { settingsService } from './settingsService';

// Базовый URL бэкенда берём из Vite-переменной окружения.
// Пример: VITE_API_BASE_URL="http://localhost:4000/api"
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
// Опциональный URL для n8n (отзывы, события PR). Если не задан, /pr/* идут на API_BASE_URL.
const N8N_BASE_URL = import.meta.env.VITE_N8N_BASE_URL || '';

function getBaseUrlForPath(path: string): string {
  if (path.startsWith('/pr/') && N8N_BASE_URL) return N8N_BASE_URL;
  return API_BASE_URL || '';
}

export const AUTH_TOKEN_KEY = 'mkd_auth_token';

export function getAuthToken(): string | null {
  return localStorage.getItem(AUTH_TOKEN_KEY);
}

export function setAuthToken(token: string): void {
  localStorage.setItem(AUTH_TOKEN_KEY, token);
}

export function clearAuth(): void {
  localStorage.removeItem(AUTH_TOKEN_KEY);
  localStorage.removeItem('mkd_portalLogin');
  window.dispatchEvent(new CustomEvent('mkd-auth-logout'));
}

/** Гостевой токен без учётки — для просмотра публичных отчётов. Запрос без Authorization. */
export async function fetchGuestToken(): Promise<string> {
  const base = API_BASE_URL || '';
  const url = base ? `${base.replace(/\/$/, '')}/auth/guest-token` : '/api/auth/guest-token';
  const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' } });
  if (!res.ok) throw new Error('Не удалось получить гостевой токен');
  const data = await res.json();
  if (!data.token) throw new Error('Нет токена в ответе');
  return data.token;
}

/** fetch with Authorization: Bearer token for /api requests (works with Vite proxy). */
export function authFetch(
  input: RequestInfo | URL,
  init?: RequestInit
): Promise<Response> {
  const token = getAuthToken();
  const headers = new Headers(init?.headers);
  if (token) {
    headers.set('Authorization', `Bearer ${token}`);
  }
  return fetch(input, { ...init, headers });
}

if (!API_BASE_URL && !import.meta.env.DEV) {
  console.warn(
    '[apiClient] VITE_API_BASE_URL не задан. В dev используется /api (proxy).'
  );
}

const isProduction = import.meta.env.PROD;
if (isProduction && API_BASE_URL && !API_BASE_URL.startsWith('https://')) {
  console.warn(
    '[apiClient] В production VITE_API_BASE_URL должен использовать HTTPS для защиты персональных данных (152-ФЗ).'
  );
}

// Кэш GET-ответов (TTL 60 сек) для списков и справочников — меньше запросов при переключении вкладок
const GET_CACHE_TTL_MS = 60 * 1000;
const cacheablePathPrefixes = ['/buildings', '/districts'];
const responseCache = new Map<string, { data: unknown; expiresAt: number }>();

function getCacheKey(path: string, method: string): string | null {
  if (method !== 'GET') return null;
  const normalized = path.split('?')[0];
  if (cacheablePathPrefixes.some((p) => normalized === p || normalized.startsWith(p + '/'))) {
    return path;
  }
  return null;
}

export function clearGetCache(pathPrefix?: string): void {
  if (!pathPrefix) {
    responseCache.clear();
    return;
  }
  for (const key of responseCache.keys()) {
    const norm = key.split('?')[0];
    if (norm === pathPrefix || norm.startsWith(pathPrefix + '/')) {
      responseCache.delete(key);
    }
  }
}

async function request<T>(path: string, options: RequestInit = {}): Promise<T> {
  const baseUrl = getBaseUrlForPath(path);
  if (!baseUrl) {
    throw new Error('API_BASE_URL is not configured');
  }

  const url = `${baseUrl}${path}`;
  const method = options.method || 'GET';
  const body = options.body;

  const cacheKey = getCacheKey(path, method);
  if (cacheKey) {
    const entry = responseCache.get(cacheKey);
    if (entry && Date.now() < entry.expiresAt) {
      return entry.data as T;
    }
  }

  // Проверяем статус подключения
  const connectionStatus = connectionService.getStatus();
  const isOffline = connectionStatus === 'disconnected';

  // Если нет подключения
  if (isOffline) {
    // DELETE запросы не кэшируются - требуют подключения
    if (method === 'DELETE') {
      throw new Error('Нет подключения к серверу. Удаление невозможно без подключения.');
    }
    // Запросы входа не кэшируем (не хранить пароль в localStorage)
    if (path === '/auth/login' || path.startsWith('/auth/login')) {
      throw new Error('Нет подключения к серверу. Вход невозможен без подключения.');
    }
    // Для POST/PUT запросов кэшируем
    if (method === 'POST' || method === 'PUT') {
      offlineCacheService.cacheRequest(method, path, body ? JSON.parse(body as string) : undefined);
      return Promise.resolve({} as T);
    }
    // Для GET запросов просто выбрасываем ошибку
    throw new Error('Нет подключения к серверу.');
  }

  const token = getAuthToken();
  const headers: Record<string, string> = {};
  
  // Не устанавливаем Content-Type для FormData - браузер сделает это сам с boundary
  const isFormData = body instanceof FormData;
  if (!isFormData) {
    headers['Content-Type'] = 'application/json';
  }
  
  // Добавляем пользовательские заголовки
  if (options.headers) {
    Object.assign(headers, options.headers);
  }
  
  if (token) {
    headers['Authorization'] = `Bearer ${token}`;
  }

  try {
    const response = await fetch(url, {
      ...options,
      headers,
    });

    if (response.status === 401) {
      clearAuth();
    }

    if (!response.ok) {
      // Если запрос к n8n вернул 500 и есть локальный бэкенд - пробуем fallback
      const isN8nRequest = path.startsWith('/pr/') && N8N_BASE_URL && baseUrl === N8N_BASE_URL;
      const isServerError = response.status >= 500 && response.status < 600;
      
      if (isN8nRequest && isServerError && API_BASE_URL) {
        console.warn(`[apiClient] n8n вернул ${response.status} для ${path}, пробуем локальный бэкенд...`);
        try {
          const fallbackUrl = `${API_BASE_URL}${path}`;
          const fallbackResponse = await fetch(fallbackUrl, {
            ...options,
            headers: { ...headers, ...(options.headers || {}) },
          });
          
          if (fallbackResponse.status === 401) {
            clearAuth();
          }
          
          if (fallbackResponse.ok) {
            const fallbackData = await fallbackResponse.json() as T;
            if (cacheKey) {
              responseCache.set(cacheKey, { data: fallbackData, expiresAt: Date.now() + GET_CACHE_TTL_MS });
            }
            return fallbackData;
          }
        } catch (fallbackError) {
          console.warn('[apiClient] Fallback на локальный бэкенд также не удался:', fallbackError);
        }
      }
      
      let errorMessage = `API error ${response.status}: ${response.statusText}`;
      try {
        const errorData = await response.json();
        if (errorData.error) {
          errorMessage = errorData.error;
        } else if (errorData.message) {
          errorMessage = errorData.message;
        }
      } catch {
        const text = await response.text().catch(() => '');
        if (text) {
          try {
            const parsed = JSON.parse(text);
            errorMessage = parsed.error || parsed.message || errorMessage;
          } catch {
            if (text) errorMessage = text;
          }
        }
      }
      const error: any = new Error(errorMessage);
      error.status = response.status;
      error.response = response;
      throw error;
    }

    const data = await response.json() as T;
    if (cacheKey) {
      responseCache.set(cacheKey, { data, expiresAt: Date.now() + GET_CACHE_TTL_MS });
    }
    if (method !== 'GET') {
      if (path.startsWith('/buildings')) clearGetCache('/buildings');
      if (path.startsWith('/districts')) clearGetCache('/districts');
    }
    return data;
  } catch (error) {
    // Если ошибка сети
    if (error instanceof TypeError && error.message.includes('fetch')) {
      // DELETE запросы не кэшируются
      if (method === 'DELETE') {
        throw new Error('Ошибка сети. Удаление невозможно без подключения.');
      }
      // Запросы входа не кэшируем (не хранить пароль)
      if (path === '/auth/login' || path.startsWith('/auth/login')) {
        throw new Error('Ошибка сети. Вход невозможен без подключения.');
      }
      // Для POST/PUT кэшируем
      if (method === 'POST' || method === 'PUT') {
        offlineCacheService.cacheRequest(method, path, body ? JSON.parse(body as string) : undefined);
        return Promise.resolve({} as T);
      }
    }
    throw error;
  }
}

// Функция синхронизации кэшированных запросов
export async function syncCachedRequests(): Promise<{ success: number; failed: number }> {
  if (!API_BASE_URL) {
    return { success: 0, failed: 0 };
  }
  return await offlineCacheService.syncCachedRequests(API_BASE_URL);
}

export const apiClient = {
  get: <T>(path: string) => request<T>(path, { method: 'GET' }),
  post: <T>(path: string, body?: unknown, options?: RequestInit) =>
    request<T>(path, {
      method: 'POST',
      body: body instanceof FormData ? body : (body ? JSON.stringify(body) : undefined),
      ...options,
    }),
  put: <T>(path: string, body?: unknown, options?: RequestInit) =>
    request<T>(path, {
      method: 'PUT',
      body: body instanceof FormData ? body : (body ? JSON.stringify(body) : undefined),
      ...options,
    }),
  patch: <T>(path: string, body?: unknown) =>
    request<T>(path, {
      method: 'PATCH',
      body: body ? JSON.stringify(body) : undefined,
    }),
  delete: <T>(path: string) => request<T>(path, { method: 'DELETE' }),
};

export interface NotificationItem {
  id: number;
  type: string;
  title: string;
  body: string | null;
  entityType: string | null;
  entityId: string | null;
  payload: Record<string, unknown> | null;
  readAt: string | null;
  createdAt: string;
}

// Примеры специализированных вызовов — чтобы было проще настраивать бэкенд.
// Вы можете использовать их или удалить, если не нужны.
export const backendApi = {
  // GET /districts -> District[]
  getDistricts: () => apiClient.get<District[]>('/districts'),
  // POST /districts -> создать участок
  createDistrict: (payload: { name: string; managerName: string; inventory?: BuildingInventoryItem[] }) =>
    apiClient.post<District>('/districts', payload),
  // PUT /districts/:id -> обновить участок (включая inventory)
  updateDistrict: (id: string, payload: Partial<District>) =>
    apiClient.put<District>(`/districts/${id}`, payload),
  // DELETE /districts/:id -> удалить участок
  deleteDistrict: (id: string) =>
    apiClient.delete<{ success: boolean; message: string }>(`/districts/${id}`),

  // Справочник должностей
  getPositions: () => apiClient.get<Position[]>('/positions'),
  createPosition: (payload: { name: string; isManagerial?: boolean; sortOrder?: number }) =>
    apiClient.post<Position>('/positions', payload),
  updatePosition: (id: string, payload: Partial<Pick<Position, 'name' | 'isManagerial' | 'sortOrder'>>) =>
    apiClient.put<Position>(`/positions/${id}`, payload),
  deletePosition: (id: string) => apiClient.delete(`/positions/${id}`),

  // GET /buildings -> Building[]
  getBuildings: () => apiClient.get<Building[]>('/buildings'),
  // GET /buildings/:id -> один дом с accounts (для выбора жителей)
  getBuilding: (id: string) => apiClient.get<Building>(`/buildings/${id}`),
  // POST /buildings -> создать дом (ожидает полный объект Building)
  createBuilding: (building: Building) =>
    apiClient.post<Building>('/buildings', building),
  // PUT /buildings/:id -> обновить дом (паспорт и др. поля)
  updateBuilding: (building: Building) =>
    apiClient.put<Building>(`/buildings/${building.id}`, building),
  // DELETE /buildings/:id -> удалить дом
  deleteBuilding: (id: string) =>
    apiClient.delete<{ success: boolean; message: string }>(`/buildings/${id}`),

  // GET /applications -> DomaApplication[]
  getApplications: () => apiClient.get<DomaApplication[]>('/applications'),

  // GET /applications/:id -> одна заявка (карточка)
  getApplication: (id: number) => apiClient.get<DomaApplication>(`/applications/${id}`),

  // PATCH /applications/:id -> обновление заявки (статус, срок, исполнитель и т.д.) + история
  // statusReason обязателен при status=canceled или status=deferred; при deferred также нужен deadlineAt
  updateApplication: (id: number, payload: { status?: string; deadlineAt?: string; statusReason?: string; executorName?: string; responsibleName?: string; observersText?: string; address?: string; buildingId?: string; apartment?: string; contactName?: string; contactPhone?: string; description?: string; changedBy?: string }) =>
    apiClient.patch<DomaApplication>(`/applications/${id}`, payload),

  // GET /applications/:id/history -> история изменений
  getApplicationHistory: (id: number) => apiClient.get<ApplicationHistoryEntry[]>(`/applications/${id}/history`),

  // GET /applications/:id/comments -> комментарии (?type=internal|resident)
  getApplicationComments: (id: number, type?: 'internal' | 'resident') =>
    apiClient.get<ApplicationComment[]>(`/applications/${id}/comments${type ? `?type=${type}` : ''}`),

  // POST /applications/:id/comments -> добавить комментарий (internal — без жителя)
  addApplicationComment: (id: number, payload: { text: string; type?: 'internal' | 'resident'; authorName?: string }) =>
    apiClient.post<ApplicationComment>(`/applications/${id}/comments`, payload),

  // POST /applications -> создание заявки вручную (диспетчерская)
  createApplication: (payload: CreateApplicationPayload) =>
    apiClient.post<DomaApplication>('/applications', payload),

  // ========= ЖУРНАЛ ОТКЛЮЧЕНИЙ (OUTAGES) =========
  getOutages: (params?: { buildingId?: string; active?: boolean }) => {
    const search = new URLSearchParams();
    if (params?.buildingId) search.set('buildingId', params.buildingId);
    if (params?.active !== undefined) search.set('active', String(params.active));
    const q = search.toString();
    return apiClient.get<Outage[]>(`/outages${q ? `?${q}` : ''}`);
  },
  getOutage: (id: number) => apiClient.get<Outage>(`/outages/${id}`),
  createOutage: (payload: import('../types').CreateOutagePayload) =>
    apiClient.post<Outage | { created: number; items: Outage[] }>('/outages', payload),
  updateOutage: (id: number, payload: { endAt?: string; type?: string; description?: string; active?: boolean; category?: string; problemDetail?: string; workType?: string; residentMessage?: string; generateNews?: boolean }) =>
    apiClient.patch<Outage>(`/outages/${id}`, payload),

  // ========= УВЕДОМЛЕНИЯ (КОЛОКОЛЬЧИК) =========
  getNotifications: (params?: { limit?: number; offset?: number; type?: string }) => {
    const search = new URLSearchParams();
    if (params?.limit != null) search.set('limit', String(params.limit));
    if (params?.offset != null) search.set('offset', String(params.offset));
    if (params?.type) search.set('type', params.type);
    const q = search.toString();
    return apiClient.get<NotificationItem[]>(`/notifications${q ? `?${q}` : ''}`);
  },
  getUnreadCount: () => apiClient.get<{ count: number }>('/notifications/unread-count'),
  markNotificationRead: (id: number) => apiClient.patch<{ id: number; readAt: string }>(`/notifications/${id}`),
  markAllNotificationsRead: () => apiClient.patch<{ success: boolean }>('/notifications/read-all'),

  // ========= НОВОСТИ КОМПАНИИ (company-news) =========
  getCompanyNews: (params?: { status?: string; limit?: number; offset?: number }) => {
    const search = new URLSearchParams();
    if (params?.status) search.set('status', params.status);
    if (params?.limit != null) search.set('limit', String(params.limit));
    if (params?.offset != null) search.set('offset', String(params.offset));
    const q = search.toString();
    return apiClient.get<CompanyNews[]>(`/company-news${q ? `?${q}` : ''}`);
  },
  getCompanyNewsById: (id: number) => apiClient.get<CompanyNews>(`/company-news/${id}`),
  createCompanyNews: (payload: { title: string; body?: string | null; status?: CompanyNews['status']; notifyDepartments?: Department[]; notifyEmployeeIds?: string[] }) =>
    apiClient.post<CompanyNews>('/company-news', payload),
  updateCompanyNews: (id: number, payload: { title?: string; body?: string | null; status?: CompanyNews['status']; notifyDepartments?: Department[]; notifyEmployeeIds?: string[] }) =>
    apiClient.put<CompanyNews>(`/company-news/${id}`, payload),
  deleteCompanyNews: (id: number) => apiClient.delete<{ success: boolean }>(`/company-news/${id}`),

  // ========= DOMA.AI МАППИНГИ И ОЖИДАЮЩИЕ СОПОСТАВЛЕНИЯ =========

  // POST/GET /doma/sync-now -> запустить синхронизацию заявок из Doma AI (требует авторизации или ?secret=DOMA_SYNC_SECRET)
  domaSyncNow: () =>
    apiClient.post<{ synced: number; message?: string }>('/doma/sync-now', {}),

  // GET /doma/mappings -> существующие сопоставления адресов и сотрудников
  getDomaMappings: () =>
    apiClient.get<{
      success: boolean;
      data: {
        addresses: Array<{
          id: number;
          domaAddress: string;
          buildingId: string | null;
          buildingAddress: string | null;
          createdAt: string;
        }>;
        employees: Array<{
          id: number;
          domaName: string;
          employeeId: string | null;
          employeeName: string | null;
          createdAt: string;
        }>;
      };
    }>('/doma/mappings'),

  // GET /doma/pending-mappings -> ожидающие сопоставления
  getDomaPendingMappings: () =>
    apiClient.get<{
      success: boolean;
      data: {
        buildings: Array<{
          id: number;
          domaValue: string;
          suggestedId?: string | null;
          suggestedName?: string | null;
          createdAt: string;
        }>;
        employees: Array<{
          id: number;
          domaValue: string;
          suggestedId?: string | null;
          suggestedName?: string | null;
          createdAt: string;
        }>;
      };
    }>('/doma/pending-mappings'),

  // POST /doma/pending-mappings/:id/resolve -> ручное сопоставление
  resolveDomaPendingMapping: (id: number, targetId: string) =>
    apiClient.post<{ success: boolean }>(`/doma/pending-mappings/${id}/resolve`, {
      targetId,
    }),

  // ========= АВТОРИЗАЦИЯ =========
  login: (payload: { login: string; password: string; captchaToken?: string }) =>
    apiClient.post<{ token: string; user: { id: string; userId: number; name: string; role: string; avatar: string | null } }>('/auth/login', payload),
  getMe: () =>
    apiClient.get<User>('/auth/me'),
  updateProfile: (data: Partial<User>) =>
    apiClient.put<User>('/auth/profile', data),
  uploadProfilePhoto: async (file: File) => {
    const form = new FormData();
    form.append('photo', file);
    const base = API_BASE_URL || '/api';
    const r = await authFetch(`${base}${base.endsWith('/') ? '' : '/'}auth/profile/photo`, {
      method: 'POST',
      body: form,
    });
    if (!r.ok) {
      const err = await r.json().catch(() => ({ error: r.statusText }));
      throw new Error(err.error || r.statusText);
    }
    return r.json() as Promise<{ success: boolean; photoUrl: string }>;
  },
  deleteProfilePhoto: () =>
    apiClient.delete<{ success: boolean }>('/auth/profile/photo'),
  changePassword: (oldPassword: string, newPassword: string) =>
    apiClient.put<{ success: boolean; message: string }>('/auth/change-password', { oldPassword, newPassword }),
  getPreferences: () =>
    apiClient.get<UserPreferences>('/auth/preferences'),
  updatePreferences: (prefs: Partial<UserPreferences>) =>
    apiClient.put<UserPreferences>('/auth/preferences', prefs),

  // ========= ПАНЕЛЬ УПРАВЛЕНИЯ: ПОРТАЛЬНЫЕ ПОЛЬЗОВАТЕЛИ =========
  getPortalUserByLogin: (login: string) =>
    apiClient.get<PortalUserRow>(`/portal-users/me?login=${encodeURIComponent(login)}`),
  getPortalUsers: () =>
    apiClient.get<PortalUserRow[]>('/admin/portal-users'),
  createPortalUser: (payload: { employeeId: string; login: string; email?: string; role?: string; password?: string }) =>
    apiClient.post<PortalUserRow>('/admin/portal-users', payload),
  updatePortalUserPassword: (id: number, password: string) =>
    apiClient.put<{ success: boolean }>(`/admin/portal-users/${id}/password`, { password }),
  createEmployeeWithUser: (payload: {
    name: string;
    position: string;
    phone: string;
    status?: string;
    salary: number;
    assignedDistrictId?: string;
    login: string;
    email?: string;
    role?: string;
  }) =>
    apiClient.post<{ employee: { id: string; name: string; position: string; phone: string; status: string; salary: number; assignedDistrictId: string | null }; portalUser: PortalUserRow }>('/admin/employees-with-user', payload),
  updatePortalUser: (id: number, payload: { login?: string; email?: string; role?: string; permissions?: string[] | null; scope?: 'all' | 'own_district' }) =>
    apiClient.put<PortalUserRow>(`/admin/portal-users/${id}`, payload),
  updatePortalUserPassword: (id: number, password: string) =>
    apiClient.put<{ success: boolean }>(`/admin/portal-users/${id}/password`, { password }),
  deletePortalUser: (id: number) =>
    apiClient.delete<{ success: boolean }>(`/admin/portal-users/${id}`),

  // GET/PUT настройки DaData (панель управления)
  getDadataSettings: () =>
    apiClient.get<{ enabled: boolean; apiKey: string; secret: string }>('/admin/integrations/dadata'),
  saveDadataSettings: (payload: { enabled?: boolean; apiKey?: string; secret?: string }) =>
    apiClient.put<{ success: boolean }>('/admin/integrations/dadata', payload),

  getDomaSettings: () =>
    apiClient.get<{ apiUrl: string; token: string }>('/admin/integrations/doma'),
  saveDomaSettings: (payload: { apiUrl?: string; token?: string }) =>
    apiClient.put<{ success: boolean }>('/admin/integrations/doma', payload),

  getAIChatStatus: () =>
    apiClient.get<{ enabled: boolean }>('/ai/status'),
  getAIChatSettings: () =>
    apiClient.get<{ enabled: boolean; url: string; apiKey: string }>('/admin/integrations/ai-chat'),
  saveAIChatSettings: (payload: { enabled?: boolean; url?: string; apiKey?: string }) =>
    apiClient.put<{ success: boolean }>('/admin/integrations/ai-chat', payload),

  // Резервные копии БД
  createBackup: () =>
    apiClient.post<{ filename: string; createdAt: string }>('/admin/backup', {}),
  getBackups: () =>
    apiClient.get<Array<{ filename: string; createdAt: string }>>('/admin/backups'),
  getBackupDownloadUrl: (filename: string) => {
    const base = API_BASE_URL || '';
    return `${base}/admin/backup/download/${encodeURIComponent(filename)}`;
  },

  // Дашборд безопасности: капча, логи входа, чёрный список, мониторинг
  getSecuritySettings: () =>
    apiClient.get<{ captchaEnabled: boolean; turnstileSiteKey: string | null; turnstileSecretKeySet: boolean }>('/admin/security/settings'),
  saveSecuritySettings: (payload: { turnstileSiteKey?: string; turnstileSecretKey?: string }) =>
    apiClient.put<{ captchaEnabled: boolean; turnstileSiteKey: string | null; turnstileSecretKeySet: boolean }>('/admin/security/settings', payload),
  getCaptchaSiteKey: () =>
    apiClient.get<{ siteKey: string | null }>('/auth/captcha-site-key'),
  getSecurityLogs: (params?: { page?: number; limit?: number; success?: boolean; from?: string; to?: string }) => {
    const q = new URLSearchParams();
    if (params?.page != null) q.append('page', String(params.page));
    if (params?.limit != null) q.append('limit', String(params.limit));
    if (params?.success === true) q.append('success', 'true');
    if (params?.success === false) q.append('success', 'false');
    if (params?.from) q.append('from', params.from);
    if (params?.to) q.append('to', params.to);
    const query = q.toString();
    return apiClient.get<{ items: Array<{ id: number; ip: string; loginMasked: string; success: boolean; createdAt: string }>; total: number; page: number; limit: number }>(
      `/admin/security/logs${query ? `?${query}` : ''}`
    );
  },
  getSecurityStats: () =>
    apiClient.get<{ failedLast24h: number; totalAttemptsLast24h: number; blockedCount: number }>('/admin/security/stats'),
  getSecurityBlacklist: () =>
    apiClient.get<Array<{ id: number; type: string; value: string; reason: string | null; createdAt: string }>>('/admin/security/blacklist'),
  addSecurityBlacklist: (payload: { type: 'ip' | 'login'; value: string; reason?: string }) =>
    apiClient.post<{ id: number; type: string; value: string; reason: string | null; createdAt: string }>('/admin/security/blacklist', payload),
  deleteSecurityBlacklist: (id: number) =>
    apiClient.delete<{ success: boolean }>(`/admin/security/blacklist/${id}`),

  // Шаблоны прав
  getPermissionTemplates: () =>
    apiClient.get<PermissionTemplateRow[]>('/admin/permission-templates'),
  createPermissionTemplate: (payload: { name: string; description?: string; permissions: string[]; scope?: 'all' | 'own_district'; forPosition?: string | null; suggestedRole?: string | null }) =>
    apiClient.post<PermissionTemplateRow>('/admin/permission-templates', payload),
  updatePermissionTemplate: (id: number, payload: { name?: string; description?: string; permissions?: string[]; scope?: 'all' | 'own_district'; forPosition?: string | null; suggestedRole?: string | null }) =>
    apiClient.put<PermissionTemplateRow>(`/admin/permission-templates/${id}`, payload),
  deletePermissionTemplate: (id: number) =>
    apiClient.delete<{ success: boolean }>(`/admin/permission-templates/${id}`),

  // HR: вакансии и кандидаты (для очистки данных)
  getVacancies: (params?: { status?: string; department?: string }) => {
    const q = new URLSearchParams();
    if (params?.status) q.append('status', params.status);
    if (params?.department) q.append('department', params.department);
    const query = q.toString();
    return apiClient.get<any[]>(`/vacancies${query ? `?${query}` : ''}`);
  },
  deleteVacancy: (id: number) =>
    apiClient.delete<{ message: string; vacancy: any }>(`/vacancies/${id}`),
  getCandidates: (params?: { vacancyId?: number; stage?: string }) => {
    const q = new URLSearchParams();
    if (params?.vacancyId != null) q.append('vacancyId', String(params.vacancyId));
    if (params?.stage) q.append('stage', params.stage);
    const query = q.toString();
    return apiClient.get<any[]>(`/candidates${query ? `?${query}` : ''}`);
  },
  deleteCandidate: (id: number) =>
    apiClient.delete<{ message: string }>(`/candidates/${id}`),

  // HR: типовые документы (кадры — печать/шаблоны)
  getHrTemplateDocuments: () =>
    apiClient.get<{ id: number; name: string; filePath: string; originalFilename: string | null; createdAt: string }[]>('/hr/template-documents'),
  getHrTemplateDocumentDownloadUrl: (id: number) => {
    const base = API_BASE_URL || '/api';
    return (base.endsWith('/api') ? base : base + '/api') + `/hr/template-documents/${id}/download`;
  },
  uploadHrTemplateDocument: (formData: FormData) => {
    const base = API_BASE_URL || '/api';
    const url = base.endsWith('/api') ? `${base}/hr/template-documents` : `${base}/api/hr/template-documents`;
    return authFetch(url, { method: 'POST', body: formData }).then((r) => r.json());
  },
  deleteHrTemplateDocument: (id: number) =>
    apiClient.delete<{ success: boolean }>(`/hr/template-documents/${id}`),

  // Обучение: программы (для очистки данных)
  getTrainingPrograms: (params?: { type?: string; category?: string; isRequired?: boolean }) => {
    const q = new URLSearchParams();
    if (params?.type) q.append('type', params.type);
    if (params?.category) q.append('category', params.category);
    if (params?.isRequired === true) q.append('isRequired', 'true');
    const query = q.toString();
    return apiClient.get<any[]>(`/training/programs${query ? `?${query}` : ''}`);
  },
  deleteTrainingProgram: (id: number) =>
    apiClient.delete<{ success?: boolean; message?: string }>(`/training/programs/${id}`),

  // Финансы: категории и статьи расходов (для очистки данных)
  getExpenseCategories: () =>
    apiClient.get<any[]>('/finance/expense-categories'),
  deleteExpenseCategory: (id: number) =>
    apiClient.delete<{ success: boolean; message: string }>(`/finance/expense-categories/${id}`),
  getExpenseItems: (params?: { categoryId?: number }) => {
    const q = params?.categoryId != null ? `?categoryId=${params.categoryId}` : '';
    return apiClient.get<any[]>(`/finance/expense-items${q}`);
  },
  deleteExpenseItem: (id: number) =>
    apiClient.delete<{ success: boolean; message?: string }>(`/finance/expense-items/${id}`),

  // Офис: заявки на ТМЦ, склад, документы (для очистки данных)
  getSupplyRequests: (params?: { status?: string; category?: string }) => {
    const q = new URLSearchParams();
    if (params?.status) q.append('status', params.status);
    if (params?.category) q.append('category', params.category);
    const query = q.toString();
    return apiClient.get<any[]>(`/office/supply-requests${query ? `?${query}` : ''}`);
  },
  deleteSupplyRequest: (id: number) =>
    apiClient.delete<{ message: string; id: number }>(`/office/supply-requests/${id}`),
  getOfficeInventory: (params?: { category?: string; search?: string }) => {
    const q = new URLSearchParams();
    if (params?.category) q.append('category', params.category);
    if (params?.search) q.append('search', params.search);
    const query = q.toString();
    return apiClient.get<any[]>(`/office/inventory${query ? `?${query}` : ''}`);
  },
  deleteOfficeInventory: (id: number) =>
    apiClient.delete<{ message: string; id: number }>(`/office/inventory/${id}`),
  getOfficeDocuments: (params?: { status?: string; documentType?: string; search?: string }) => {
    const q = new URLSearchParams();
    if (params?.status) q.append('status', params.status);
    if (params?.documentType) q.append('documentType', params.documentType);
    if (params?.search) q.append('search', params.search);
    const query = q.toString();
    return apiClient.get<any[]>(`/office/documents${query ? `?${query}` : ''}`);
  },
  deleteOfficeDocument: (id: number) =>
    apiClient.delete<{ success?: boolean; message?: string }>(`/office/documents/${id}`),

  // Doma: удаление сопоставлений (для очистки данных)
  deleteDomaAddressMapping: (id: number) =>
    apiClient.delete<{ success: boolean }>(`/doma/mappings/address/${id}`),
  deleteDomaEmployeeMapping: (id: number) =>
    apiClient.delete<{ success: boolean }>(`/doma/mappings/employee/${id}`),

  // PR: фото работ и мероприятия (для очистки данных)
  getWorkPhotos: (params?: { building_id?: string; resident_report_id?: string; task_id?: string }) => {
    const q = new URLSearchParams();
    if (params?.building_id) q.append('building_id', params.building_id);
    if (params?.resident_report_id) q.append('resident_report_id', params.resident_report_id);
    if (params?.task_id) q.append('task_id', params.task_id);
    const query = q.toString();
    return apiClient.get<any[]>(`/pr/work-photos${query ? `?${query}` : ''}`);
  },
  deleteWorkPhoto: (id: number) =>
    apiClient.delete<{ success?: boolean }>(`/pr/work-photos/${id}`),

  // Загрузка данных: шаблоны и импорт
  getImportTemplateUrl: (type: 'districts' | 'buildings' | 'employees' | 'accounts') => {
    const base = API_BASE_URL || '';
    return `${base}/admin/import/templates/${type}`;
  },
  importData: (type: 'districts' | 'buildings' | 'employees' | 'accounts', file: File) => {
    const formData = new FormData();
    formData.append('file', file);
    const base = API_BASE_URL || '';
    return fetch(`${base}/admin/import/${type}`, {
      method: 'POST',
      body: formData,
    }).then(async (r) => {
      if (!r.ok) {
        const err = await r.json().catch(() => ({ error: r.statusText }));
        throw new Error(err.error || r.statusText);
      }
      return r.json() as Promise<{ created: number; errors: Array<{ row: number; message: string }>; total: number }>;
    });
  },

  // ========= СТАТИСТИКА ПРОИЗВОДИТЕЛЬНОСТИ =========
  
  // GET /performance/overall -> общая статистика по организации
  getOverallPerformance: () => apiClient.get<{
    success: boolean;
    data: {
      total: number;
      completed: number;
      overdue: number;
      inProgress: number;
      completionRate: number;
      overdueRate: number;
      periodStart: string;
      periodEnd: string;
    };
  }>('/performance/overall'),

  // GET /performance/employees -> статистика по сотрудникам
  getEmployeePerformance: () => apiClient.get<{
    success: boolean;
    data: Array<{
      employeeName: string;
      districtId: string | null;
      districtName: string | null;
      totalAssigned: number;
      totalCompleted: number;
      totalOverdue: number;
      totalInProgress: number;
      totalDeferred: number;
      completionRate: number;
      overdueRate: number;
      performanceScore: number;
    }>;
  }>('/performance/employees'),

  // GET /performance/districts -> статистика по участкам
  getDistrictPerformance: () => apiClient.get<{
    success: boolean;
    data: Array<{
      districtId: string;
      districtName: string;
      managerName: string;
      totalApplications: number;
      totalCompleted: number;
      totalOverdue: number;
      totalInProgress: number;
      completionRate: number;
      overdueRate: number;
      averageScore: number;
    }>;
  }>('/performance/districts'),

  // GET /production/metrics -> метрики производства (состояние домов, график работ, SLA)
  getProductionMetrics: () => apiClient.get<{
    success: boolean;
    data: {
      buildingCondition: {
        value: number;
        label: string;
        condition: 'excellent' | 'good' | 'fair' | 'poor';
        totalBuildings: number;
        buildingsWithWear: number;
      };
      workSchedule: {
        onTime: number;
        late: number;
        overdue: number;
        onTimeRate: number;
        lateRate: number;
      };
      slaRating: number;
    };
  }>('/production/metrics'),

  // GET /production/dashboard -> полный дашборд для продвинутой статистики производства
  getProductionDashboard: () => apiClient.get<{
    success: boolean;
    data: {
      buildingCondition: {
        value: number;
        label: string;
        condition: string;
        totalBuildings: number;
        buildingsWithWear: number;
      };
      workSchedule: { onTime: number; late: number; overdue: number; onTimeRate: number; lateRate: number };
      slaRating: number;
      applicationsTimeSlices: {
        today: { created: number; closed: number; overdueNow: number };
        week: { created: number; closed: number; overdueNow: number };
        month: { created: number; closed: number; overdueNow: number };
      };
      applicationsSummary: {
        new: number;
        inProgress: number;
        deferred: number;
        done: number;
        overdue: number;
        completionRate: number;
      };
      districts: Array<{
        districtId: string;
        districtName: string;
        managerName: string;
        buildingCount: number;
        totalApplications: number;
        totalCompleted: number;
        totalOverdue: number;
        totalInProgress: number;
        completionRate: number;
        overdueRate: number;
        averageScore: number;
      }>;
      topPerformers: Array<{ name: string; activeCount: number }>;
      planWorks: {
        total: number;
        completed: number;
        current: number;
        carriedOver: number;
        estimatedCostMonth: number;
        byDistrict: Array<{ districtId: string; total: number; completed: number; buildingCount: number }>;
      };
      inspections: {
        countMonth: number;
        countQuarter: number;
        lastDate: string | null;
        byDistrict: Array<{ districtId: string; count: number; lastDate: string | null }>;
      };
      writeOffs: {
        countToday: number;
        countWeek: number;
        countMonth: number;
        sumMonth: number;
        byDistrict: Array<{ districtId: string; count: number; sum: number }>;
      };
      meterRounds: {
        countMonth: number;
        lastDate: string | null;
        byDistrict: Array<{ districtId: string; count: number; lastDate: string | null }>;
      };
      districtInventorySummary: Array<{
        districtId: string;
        districtName: string;
        totalQuantity: number;
        totalValue: number;
      }>;
    };
  }>('/production/dashboard'),

  // GET /pr/dashboard -> полный дашборд PR для продвинутой статистики и карточки сводки
  getPRDashboard: () => apiClient.get<{
    success: boolean;
    data: {
      npsCompany: number;
      npsResponsesCount: number;
      npsAvgScore: number;
      npsPromoters: number;
      npsPassives: number;
      npsDetractors: number;
      csatPercent: number;
      reviewsAvgRating: number;
      reviewsTotal: number;
      smm: { total: number; byChannel: Array<{ id: number; name: string; type: string; subscribersCount: number }> };
      reviewsStats: {
        total: number;
        new_count: number;
        processed_count: number;
        archived_count: number;
        positive_count: number;
        negative_count: number;
        yandex_count: number;
        gis2_count: number;
      };
      incidentsSummary: { total: number; open: number; in_progress: number; resolved: number };
      events: { countMonth: number; upcoming: number; past: number };
      residentReports: { total: number; publishedThisMonth: number };
      workPhotos: { total: number; thisMonth: number };
      npsSurveys: {
        total: number;
        active: number;
        list: Array<{ id: number; title: string; responsesCount: number; status: string }>;
      };
      scheduledPosts: { draft: number; pending_approval: number; approved: number; published: number; rejected: number; edited: number };
      negative: { openIncidents: number; negativeReviewsCount: number };
    };
  }>('/pr/dashboard'),

  // GET /office/dashboard -> полный дашборд офиса для продвинутой статистики и карточки сводки
  getOfficeDashboard: () => apiClient.get<{
    success: boolean;
    data: {
      companyNews: { total: number; published: number; draft: number; pending: number; publishedThisMonth: number };
      equipment: { total: number; byCondition: { good: number; fair: number; poor: number }; warrantyExpiringSoon: number };
      repairRequests: { total: number; open: number; in_progress: number; completed: number };
      supplyRequests: { total: number; byStatus: Record<string, number> };
      inventory: { totalItems: number; lowStockCount: number };
      documents: { total: number; incoming: number; outgoing: number; byStatus: Record<string, number> };
      orders: { total: number; byStatus: Record<string, number> };
      meetings: { thisWeek: number; upcoming: number };
      meetingRooms: { total: number };
      knowledgeBase: { categoriesCount: number; articlesCount: number };
    };
  }>('/office/dashboard'),

  // PUT /applications/:number/status -> обновление статуса заявки
  updateApplicationStatus: (
    number: string,
    status: string,
    comment: string,
    deferredUntil?: string
  ) => {
    const settings = settingsService.getDomaAISettings();
    return apiClient.put<{
      success: boolean;
      data: {
        id: number;
        number: string;
        status: string;
      };
    }>(`/applications/${number}/status`, {
      status,
      comment,
      deferredUntil,
      domaApiUrl: settings?.apiUrl,
      domaToken: settings?.token,
    });
  },

  // POST /buildings/:id/accounts -> создание нового лицевого счета
  createAccount: (buildingId: string, account: Partial<PersonalAccount>) =>
    apiClient.post<PersonalAccount>(`/buildings/${buildingId}/accounts`, account),

  // PUT /buildings/:id/accounts/:accountId -> обновление лицевого счета
  updateAccount: (buildingId: string, account: PersonalAccount) =>
    apiClient.put<PersonalAccount>(`/buildings/${buildingId}/accounts/${account.id}`, account),

  // DELETE /buildings/:id/accounts/:accountId -> удаление лицевого счета
  deleteAccount: (buildingId: string, accountId: string) =>
    apiClient.delete<{ success: boolean; message: string }>(`/buildings/${buildingId}/accounts/${accountId}`),

  // GET /employees -> Employee[]
  getEmployees: () => apiClient.get<Employee[]>('/employees'),
  getEmployeesList: () => apiClient.get<Array<{ id: string; name: string }>>('/employees/list'),
  deleteEmployee: (id: string) =>
    apiClient.delete<{ success: boolean; message: string }>(`/employees/${id}`),

  // GET /responsibility -> зоны ответственности (section + subId -> employeeIds)
  getResponsibility: (params?: { section?: string }) => {
    const q = params?.section ? `?section=${encodeURIComponent(params.section)}` : '';
    return apiClient.get<{ assignments: Array<{ employeeId: string; section: string; subId: string }> }>(`/responsibility${q}`);
  },
  // PUT /responsibility -> установить ответственных за зону
  putResponsibility: (payload: { section: string; subId: string; employeeIds: string[] }) =>
    apiClient.put<{ ok: boolean; section: string; subId: string; count: number }>('/responsibility', payload),

  // ========= ЮРИДИЧЕСКИЙ ОТДЕЛ: ДОСУДЕБНАЯ РАБОТА =========
  
  // GET /legal/debtors -> LegalDebtor[]
  getLegalDebtors: (params?: { status?: string; buildingId?: string; search?: string }) => {
    const queryParams = new URLSearchParams();
    if (params?.status) queryParams.append('status', params.status);
    if (params?.buildingId) queryParams.append('buildingId', params.buildingId);
    if (params?.search) queryParams.append('search', params.search);
    const query = queryParams.toString();
    return apiClient.get<LegalDebtor[]>(`/legal/debtors${query ? `?${query}` : ''}`);
  },
  
  // POST /legal/debtors -> создать должника
  createLegalDebtor: (debtor: {
    buildingId?: string;
    apartment: string;
    debtorName?: string;
    phone?: string;
    email?: string;
    address: string;
    debtAmount: number;
    debtMonths: number;
  }) => apiClient.post<LegalDebtor>('/legal/debtors', debtor),

  // GET /legal/pre-trial-work -> PreTrialWork[]
  getPreTrialWork: (params?: { status?: string; assignedTo?: string; search?: string }) => {
    const queryParams = new URLSearchParams();
    if (params?.status) queryParams.append('status', params.status);
    if (params?.assignedTo) queryParams.append('assignedTo', params.assignedTo);
    if (params?.search) queryParams.append('search', params.search);
    const query = queryParams.toString();
    return apiClient.get<PreTrialWork[]>(`/legal/pre-trial-work${query ? `?${query}` : ''}`);
  },

  // POST /legal/pre-trial-work -> создать досудебную работу
  createPreTrialWork: (work: {
    debtorId: number;
    assignedTo?: string;
    notes?: string;
  }) => apiClient.post<PreTrialWork>('/legal/pre-trial-work', work),

  // PUT /legal/pre-trial-work/:id -> обновить досудебную работу
  updatePreTrialWork: (id: number, updates: {
    assignedTo?: string;
    status?: string;
    notes?: string;
    promisedPaymentDate?: string;
    promisedPaymentAmount?: number;
  }) => apiClient.put<PreTrialWork>(`/legal/pre-trial-work/${id}`, updates),

  // POST /legal/pre-trial-work/:id/actions -> добавить действие
  addPreTrialAction: (workId: number, action: {
    actionType: 'call' | 'letter' | 'visit';
    actionDate?: string;
    performedBy: string;
    result?: string;
    notes?: string;
    attachments?: string[];
  }) => apiClient.post<PreTrialAction>(`/legal/pre-trial-work/${workId}/actions`, action),

  // POST /legal/pre-trial-work/:id/promised-payment -> добавить обещанную оплату
  addPromisedPayment: (workId: number, payment: {
    promisedDate: string;
    promisedAmount: number;
    notes?: string;
  }) => apiClient.post<PromisedPayment>(`/legal/pre-trial-work/${workId}/promised-payment`, payment),

  // PUT /legal/pre-trial-work/:id/transfer-to-court -> передать в суд
  transferToCourt: (workId: number, courtCaseId?: string) =>
    apiClient.put<PreTrialWork>(`/legal/pre-trial-work/${workId}/transfer-to-court`, { courtCaseId }),

  // PUT /legal/promised-payments/:id/mark-paid -> отметить оплату как выполненную
  markPromisedPaymentPaid: (paymentId: number, data: {
    actualPaymentDate?: string;
    actualPaymentAmount?: number;
  }) => apiClient.put<PromisedPayment>(`/legal/promised-payments/${paymentId}/mark-paid`, data),

  // ========= ПРОВЕРКА КОНТРАГЕНТОВ =========
  
  // POST /legal/check-counterparty -> проверить контрагента по ИНН через DaData
  checkCounterparty: (inn: string) =>
    apiClient.post<any>('/legal/check-counterparty', { inn }),

  // GET /legal/counterparties -> список проверенных контрагентов
  getCounterparties: (params?: { riskLevel?: string; search?: string; limit?: number }) => {
    const queryParams = new URLSearchParams();
    if (params?.riskLevel) queryParams.append('riskLevel', params.riskLevel);
    if (params?.search) queryParams.append('search', params.search);
    if (params?.limit) queryParams.append('limit', params.limit.toString());
    const query = queryParams.toString();
    return apiClient.get<any[]>(`/legal/counterparties${query ? `?${query}` : ''}`);
  },

  // GET /legal/counterparties/:id -> получить детальный отчет о проверке
  getCounterpartyReport: (id: number) => apiClient.get<any>(`/legal/counterparties/${id}`),

  // ========= ЮРИДИЧЕСКИЙ ОТДЕЛ: ДОГОВОРЫ =========
  
  // GET /legal/contracts -> список договоров
  getLegalContracts: (params?: { status?: string; search?: string; viewMode?: string }) => {
    const queryParams = new URLSearchParams();
    if (params?.status) queryParams.append('status', params.status);
    if (params?.search) queryParams.append('search', params.search);
    if (params?.viewMode) queryParams.append('viewMode', params.viewMode);
    const query = queryParams.toString();
    return apiClient.get<LegalContract[]>(`/legal/contracts${query ? `?${query}` : ''}`);
  },

  // POST /legal/contracts -> создать договор
  createLegalContract: (contract: Partial<LegalContract> & { number: string; type: string; counterparty: string; amount: number; startDate: string; endDate: string; manager: string }) =>
    apiClient.post<LegalContract>('/legal/contracts', contract),

  // PUT /legal/contracts/:id -> обновить договор
  updateLegalContract: (id: string, updates: Partial<LegalContract>) =>
    apiClient.put<LegalContract>(`/legal/contracts/${id}`, updates),

  // GET /legal/contracts/:id -> получить договор
  getLegalContract: (id: string) => apiClient.get<LegalContract>(`/legal/contracts/${id}`),

  // ========= ЮРИДИЧЕСКИЙ ОТДЕЛ: СУДЕБНЫЕ ДЕЛА =========
  
  // GET /legal/court-cases -> список судебных дел
  getLegalCourtCases: (params?: { status?: string; type?: string; role?: string; search?: string }) => {
    const queryParams = new URLSearchParams();
    if (params?.status) queryParams.append('status', params.status);
    if (params?.type) queryParams.append('type', params.type);
    if (params?.role) queryParams.append('role', params.role);
    if (params?.search) queryParams.append('search', params.search);
    const query = queryParams.toString();
    return apiClient.get<LegalCourtCase[]>(`/legal/court-cases${query ? `?${query}` : ''}`);
  },

  // POST /legal/court-cases -> создать судебное дело
  createLegalCourtCase: (courtCase: Partial<LegalCourtCase> & { caseNumber: string; type: string; role: string; subject: string; amount: number }) =>
    apiClient.post<LegalCourtCase>('/legal/court-cases', courtCase),

  // PUT /legal/court-cases/:id -> обновить судебное дело
  updateLegalCourtCase: (id: string, updates: Partial<LegalCourtCase>) =>
    apiClient.put<LegalCourtCase>(`/legal/court-cases/${id}`, updates),

  // GET /legal/court-cases/:id -> получить судебное дело
  getLegalCourtCase: (id: string) => apiClient.get<LegalCourtCase>(`/legal/court-cases/${id}`),

  // ========= МОДУЛЬ РАЗВИТИЯ =========
  
  // Pipeline (Воронка)
  getDevelopmentPipeline: (params?: { status?: string; search?: string }) => {
    const queryParams = new URLSearchParams();
    if (params?.status) queryParams.append('status', params.status);
    if (params?.search) queryParams.append('search', params.search);
    const query = queryParams.toString();
    return apiClient.get<DevPipelineItem[]>(`/development/pipeline${query ? `?${query}` : ''}`);
  },
  createDevelopmentPipeline: (item: Omit<DevPipelineItem, 'id'> & { id?: string; expected_revenue?: number }) => {
    const id = item.id || `p-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
    // Преобразуем camelCase в snake_case для бэкенда
    const payload: any = {
      id,
      address: item.address,
      type: item.type,
      floors: item.floors,
      area: item.area,
      apartments: item.apartments,
      status: item.status,
      probability: item.probability,
      expected_revenue: item.expected_revenue || item.expectedRevenue || 0,
      manager: item.manager,
      notes: item.notes || null,
    };
    return apiClient.post<DevPipelineItem>('/development/pipeline', payload);
  },
  updateDevelopmentPipeline: (id: string, updates: Partial<DevPipelineItem>) => {
    const payload: Record<string, unknown> = {};
    if (updates.address !== undefined) payload.address = updates.address;
    if (updates.type !== undefined) payload.type = updates.type;
    if (updates.floors !== undefined) payload.floors = updates.floors;
    if (updates.area !== undefined) payload.area = updates.area;
    if (updates.apartments !== undefined) payload.apartments = updates.apartments;
    if (updates.status !== undefined) payload.status = updates.status;
    if (updates.probability !== undefined) payload.probability = updates.probability;
    if (updates.expectedRevenue !== undefined) payload.expected_revenue = updates.expectedRevenue;
    if (updates.manager !== undefined) payload.manager = updates.manager;
    if (updates.buildingId !== undefined) payload.building_id = updates.buildingId;
    if (updates.notes !== undefined) payload.notes = updates.notes;
    return apiClient.put<DevPipelineItem>(`/development/pipeline/${id}`, payload);
  },
  deleteDevelopmentPipeline: (id: string) =>
    apiClient.delete<{ success: boolean; message: string }>(`/development/pipeline/${id}`),

  // OSS (Собрания)
  getDevelopmentOSS: (params?: { status?: string; building_id?: string }) => {
    const queryParams = new URLSearchParams();
    if (params?.status) queryParams.append('status', params.status);
    if (params?.building_id) queryParams.append('building_id', params.building_id);
    const query = queryParams.toString();
    return apiClient.get<DevOSSSession[]>(`/development/oss${query ? `?${query}` : ''}`);
  },
  createDevelopmentOSS: (session: Omit<DevOSSSession, 'id'> & { id?: string; building_id?: string | null; start_date?: string; end_date?: string; quorum_total?: number; agenda_items?: string[]; agendaItems?: string[] }) => {
    const id = session.id || `oss-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
    const payload: any = {
      id,
      address: session.address,
      building_id: session.building_id || session.buildingId || null,
      start_date: session.start_date || session.startDate,
      end_date: session.end_date || session.endDate,
      quorum_total: session.quorum_total || session.quorumTotal,
      quorum_current: 0,
      status: session.status || 'planned',
      type: session.type,
      description: session.description || null,
    };
    const agenda = session.agendaItems ?? session.agenda_items;
    if (agenda != null && Array.isArray(agenda) && agenda.length > 0) payload.agenda_items = agenda;
    return apiClient.post<DevOSSSession>('/development/oss', payload);
  },
  updateDevelopmentOSS: (id: string, updates: Partial<DevOSSSession> & { agenda_items?: string[]; agendaItems?: string[] }) => {
    const payload: Record<string, unknown> = {};
    if (updates.address !== undefined) payload.address = updates.address;
    if (updates.startDate !== undefined) payload.start_date = updates.startDate;
    if (updates.endDate !== undefined) payload.end_date = updates.endDate;
    if (updates.quorumTotal !== undefined) payload.quorum_total = updates.quorumTotal;
    if (updates.quorumCurrent !== undefined) payload.quorum_current = updates.quorumCurrent;
    if (updates.status !== undefined) payload.status = updates.status;
    if (updates.type !== undefined) payload.type = updates.type;
    if (updates.description !== undefined) payload.description = updates.description;
    if (updates.agendaItems !== undefined) payload.agenda_items = updates.agendaItems;
    if (updates.agenda_items !== undefined) payload.agenda_items = updates.agenda_items;
    return apiClient.put<DevOSSSession>(`/development/oss/${id}`, payload);
  },
  updateOSSQuorum: (id: string, quorumCurrent: number) =>
    apiClient.put<DevOSSSession>(`/development/oss/${id}/quorum`, { quorum_current: quorumCurrent }),
  submitOSSBallot: (id: string, ballot: { apartment: string; owner_name?: string; area: number; vote_result?: string; notes?: string; votes_by_item?: Record<string, string> }) =>
    apiClient.post<any>(`/development/oss/${id}/ballot`, ballot),
  submitOSSBallotsBulk: (id: string, ballots: { apartment: string; owner_name?: string; area: number; vote_result?: string; notes?: string; votes_by_item?: Record<string, string> }[]) =>
    apiClient.post<{ inserted: number; updated: number; total: number }>(`/development/oss/${id}/ballots/bulk`, { ballots }),
  getOSSRegistry: (id: string) =>
    apiClient.get<any[]>(`/development/oss/${id}/registry`),

  // Audits (Аудиты)
  getDevelopmentAudits: (params?: { building_id?: string }) => {
    const queryParams = new URLSearchParams();
    if (params?.building_id) queryParams.append('building_id', params.building_id);
    const query = queryParams.toString();
    return apiClient.get<DevAuditData[]>(`/development/audits${query ? `?${query}` : ''}`);
  },
  getDevelopmentAudit: (id: string) =>
    apiClient.get<DevAuditData>(`/development/audits/${id}`),
  createDevelopmentAudit: (audit: Omit<DevAuditData, 'id'> & { id?: string; building_id?: string | null; wear_percent?: number; roof_condition?: string; basement_condition?: string; calculated_tariff?: number; projected_margin?: number; audit_date?: string; auditor_name?: string | null }) => {
    const id = audit.id || `a-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
    // Преобразуем camelCase в snake_case для бэкенда
    const payload: any = {
      id,
      address: audit.address,
      building_id: audit.building_id || audit.buildingId || null,
      wear_percent: audit.wear_percent !== undefined ? audit.wear_percent : audit.wearPercent,
      roof_condition: audit.roof_condition || audit.roofCondition,
      basement_condition: audit.basement_condition || audit.basementCondition,
      calculated_tariff: audit.calculated_tariff !== undefined ? audit.calculated_tariff : audit.calculatedTariff,
      projected_margin: audit.projected_margin !== undefined ? audit.projected_margin : audit.projectedMargin,
      audit_date: audit.audit_date || audit.auditDate || new Date().toISOString().split('T')[0],
      auditor_name: audit.auditor_name || audit.auditorName || null,
      notes: audit.notes || null,
    };
    return apiClient.post<DevAuditData>('/development/audits', payload);
  },
  updateDevelopmentAudit: (id: string, updates: Partial<DevAuditData> & { inspection_data?: Record<string, unknown>; inspectionData?: Record<string, unknown> }) => {
    const payload: Record<string, unknown> = {};
    if (updates.address !== undefined) payload.address = updates.address;
    if (updates.status !== undefined) payload.status = updates.status;
    if (updates.wearPercent !== undefined) payload.wear_percent = updates.wearPercent;
    if (updates.roofCondition !== undefined) payload.roof_condition = updates.roofCondition;
    if (updates.basementCondition !== undefined) payload.basement_condition = updates.basementCondition;
    if (updates.projectedMargin !== undefined) payload.projected_margin = updates.projectedMargin;
    if (updates.complexityIndex !== undefined) payload.complexity_index = updates.complexityIndex;
    if (updates.inspectionData !== undefined) payload.inspection_data = updates.inspectionData;
    if (updates.inspection_data !== undefined) payload.inspection_data = updates.inspection_data;
    if (updates.auditDate !== undefined) payload.audit_date = updates.auditDate;
    if (updates.auditorName !== undefined) payload.auditor_name = updates.auditorName;
    if (updates.notes !== undefined) payload.notes = updates.notes;
    return apiClient.put<DevAuditData>(`/development/audits/${id}`, Object.keys(payload).length ? payload : updates);
  },
  getAuditDefectList: (id: string) =>
    apiClient.get<any>(`/development/audits/${id}/defect-list`),

  // Marketing (Маркетинг)
  getDevelopmentMarketing: (params?: { status?: string; building_id?: string }) => {
    const queryParams = new URLSearchParams();
    if (params?.status) queryParams.append('status', params.status);
    if (params?.building_id) queryParams.append('building_id', params.building_id);
    const query = queryParams.toString();
    return apiClient.get<DevMarketingActivity[]>(`/development/marketing${query ? `?${query}` : ''}`);
  },
  createDevelopmentMarketing: (activity: Omit<DevMarketingActivity, 'id'> & { id?: string; building_id?: string | null; activists_count?: number; meetings_held?: number; ads_distributed?: number }) => {
    const id = activity.id || `m-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
    // Преобразуем camelCase в snake_case для бэкенда
    const payload: any = {
      id,
      address: activity.address,
      building_id: activity.building_id || activity.buildingId || null,
      activists_count: activity.activists_count !== undefined ? activity.activists_count : activity.activistsCount || 0,
      meetings_held: activity.meetings_held !== undefined ? activity.meetings_held : activity.meetingsHeld || 0,
      ads_distributed: activity.ads_distributed !== undefined ? activity.ads_distributed : activity.adsDistributed || 0,
      competitor: activity.competitor || null,
      status: activity.status,
      notes: activity.notes || null,
    };
    return apiClient.post<DevMarketingActivity>('/development/marketing', payload);
  },
  updateDevelopmentMarketing: (id: string, updates: Partial<DevMarketingActivity> & { activists_count?: number; meetings_held?: number; ads_distributed?: number }) => {
    // Преобразуем camelCase в snake_case для бэкенда
    const payload: any = {};
    if (updates.address !== undefined) payload.address = updates.address;
    if (updates.activistsCount !== undefined || updates.activists_count !== undefined) {
      payload.activists_count = updates.activists_count !== undefined ? updates.activists_count : updates.activistsCount;
    }
    if (updates.meetingsHeld !== undefined || updates.meetings_held !== undefined) {
      payload.meetings_held = updates.meetings_held !== undefined ? updates.meetings_held : updates.meetingsHeld;
    }
    if (updates.adsDistributed !== undefined || updates.ads_distributed !== undefined) {
      payload.ads_distributed = updates.ads_distributed !== undefined ? updates.ads_distributed : updates.adsDistributed;
    }
    if (updates.competitor !== undefined) payload.competitor = updates.competitor;
    if (updates.status !== undefined) payload.status = updates.status;
    if (updates.notes !== undefined) payload.notes = updates.notes;
    return apiClient.put<DevMarketingActivity>(`/development/marketing/${id}`, payload);
  },
  updateMarketingMetrics: (id: string, metrics: { activists_count?: number; meetings_held?: number; ads_distributed?: number }) =>
    apiClient.put<DevMarketingActivity>(`/development/marketing/${id}/metrics`, metrics),

  // Summary (Сводка)
  getDevelopmentSummary: () =>
    apiClient.get<{ growthM2: number; ossSuccessRate: number; cac: number; potentialRevenue: number }>('/development/summary'),

  // Locations (Карта)
  getDevelopmentLocations: (params?: { status?: string }) => {
    const queryParams = new URLSearchParams();
    if (params?.status) queryParams.append('status', params.status);
    const query = queryParams.toString();
    return apiClient.get<any[]>(`/development/locations${query ? `?${query}` : ''}`);
  },
  createDevelopmentLocation: (location: { building_id?: string; address: string; latitude?: number; longitude?: number; status: string; competitor_name?: string }) =>
    apiClient.post<any>('/development/locations', location),

  // PR Мероприятия
  getPREvents: (params?: { status?: string; type?: string; buildingId?: string; districtId?: string; from?: string; to?: string; limit?: number }) => {
    const q = new URLSearchParams();
    if (params?.status) q.append('status', params.status);
    if (params?.type) q.append('type', params.type);
    if (params?.buildingId) q.append('buildingId', params.buildingId);
    if (params?.districtId) q.append('districtId', params.districtId);
    if (params?.from) q.append('from', params.from);
    if (params?.to) q.append('to', params.to);
    if (params?.limit != null) q.append('limit', String(params.limit));
    const query = q.toString();
    return apiClient.get<PREvent[]>(`/pr/events${query ? `?${query}` : ''}`);
  },
  getPREvent: (id: string | number) => apiClient.get<PREvent>(`/pr/events/${id}`),
  createPREvent: (payload: Partial<PREvent> & { title: string; date: string; type: PREvent['type']; category: PREvent['category']; assignedEmployeeIds?: string[] }) =>
    apiClient.post<PREvent>('/pr/events', payload),
  updatePREvent: (id: string | number, payload: Partial<PREvent> & { assignedEmployeeIds?: string[] }) =>
    apiClient.put<PREvent>(`/pr/events/${id}`, payload),
  deletePREvent: (id: string | number) => apiClient.delete<{ success: boolean }>(`/pr/events/${id}`),
  getPREventPhotos: (eventId: string | number) => apiClient.get<PREventPhoto[]>(`/pr/events/${eventId}/photos`),
  uploadPREventPhoto: (eventId: string | number, formData: FormData) =>
    apiClient.post<PREventPhoto>(`/pr/events/${eventId}/photos`, formData),
  deletePREventPhoto: (eventId: string | number, photoId: number) =>
    apiClient.delete<{ success: boolean }>(`/pr/events/${eventId}/photos/${photoId}`),

  // PR SMM каналы и подписчики
  getSMMChannels: () => apiClient.get<SMMChannel[]>('/pr/smm-channels'),
  getSMMChannelsSummary: () => apiClient.get<SMMChannelsSummary>('/pr/smm-channels/summary'),
  createSMMChannel: (payload: { name: string; type: 'tg' | 'vk' | 'wa' | 'other'; url?: string; sortOrder?: number }) =>
    apiClient.post<SMMChannel>('/pr/smm-channels', payload),
  updateSMMChannel: (id: number, payload: Partial<Pick<SMMChannel, 'name' | 'type' | 'url' | 'sortOrder'>>) =>
    apiClient.put<SMMChannel>(`/pr/smm-channels/${id}`, payload),
  deleteSMMChannel: (id: number) => apiClient.delete<{ success: boolean }>(`/pr/smm-channels/${id}`),
  createSMMChannelSnapshot: (id: number, payload: { subscribersCount: number; note?: string; recordedAt?: string }) =>
    apiClient.post<SMMSubscriberSnapshot>(`/pr/smm-channels/${id}/snapshot`, payload),

  // PR Привлечение
  getAttractionActions: (params?: { channelId?: number; actionType?: string; from?: string; to?: string; limit?: number }) => {
    const q = new URLSearchParams();
    if (params?.channelId != null) q.append('channelId', String(params.channelId));
    if (params?.actionType) q.append('actionType', params.actionType);
    if (params?.from) q.append('from', params.from);
    if (params?.to) q.append('to', params.to);
    if (params?.limit != null) q.append('limit', String(params.limit));
    const query = q.toString();
    return apiClient.get<AttractionAction[]>(`/pr/attraction-actions${query ? `?${query}` : ''}`);
  },
  createAttractionAction: (payload: { title: string; description?: string; channelId?: number; actionType: AttractionAction['actionType']; actionDate?: string; newSubscribersAttributed?: number; eventId?: number }) =>
    apiClient.post<AttractionAction>('/pr/attraction-actions', payload),
  updateAttractionAction: (id: number, payload: Partial<Pick<AttractionAction, 'title' | 'description' | 'channelId' | 'actionType' | 'actionDate' | 'newSubscribersAttributed' | 'eventId'>>) =>
    apiClient.put<AttractionAction>(`/pr/attraction-actions/${id}`, payload),
  deleteAttractionAction: (id: number) => apiClient.delete<{ success: boolean }>(`/pr/attraction-actions/${id}`),

  // PR График публикации (темы постов)
  getPostTopics: (params?: { month?: string; status?: string; limit?: number }) => {
    const q = new URLSearchParams();
    if (params?.month) q.append('month', params.month);
    if (params?.status) q.append('status', params.status);
    if (params?.limit != null) q.append('limit', String(params.limit));
    const query = q.toString();
    return apiClient.get<PostTopic[]>(`/pr/post-topics${query ? `?${query}` : ''}`);
  },
  createPostTopic: (payload: { title: string; description?: string; scheduledDate: string; month: string; status?: PostTopic['status']; createdBy?: string }) =>
    apiClient.post<PostTopic>('/pr/post-topics', payload),
  updatePostTopic: (id: number, payload: Partial<Pick<PostTopic, 'title' | 'description' | 'scheduledDate' | 'status'>>) =>
    apiClient.put<PostTopic>(`/pr/post-topics/${id}`, payload),
  approvePostTopic: (id: number, payload?: { approvedBy?: string }) =>
    apiClient.post<PostTopic>(`/pr/post-topics/${id}/approve`, payload || {}),
  rejectPostTopic: (id: number, payload: { rejectionReason?: string; approvedBy?: string }) =>
    apiClient.post<PostTopic>(`/pr/post-topics/${id}/reject`, payload),
  deletePostTopic: (id: number) => apiClient.delete<{ success: boolean }>(`/pr/post-topics/${id}`),

  // PR Отложенные посты
  getScheduledPosts: (params?: { status?: string; topicId?: number; from?: string; to?: string; limit?: number }) => {
    const q = new URLSearchParams();
    if (params?.status) q.append('status', params.status);
    if (params?.topicId != null) q.append('topicId', String(params.topicId));
    if (params?.from) q.append('from', params.from);
    if (params?.to) q.append('to', params.to);
    if (params?.limit != null) q.append('limit', String(params.limit));
    const query = q.toString();
    return apiClient.get<ScheduledPost[]>(`/pr/scheduled-posts${query ? `?${query}` : ''}`);
  },
  createScheduledPost: (payload: { title: string; content: string; channelIds?: number[]; scheduledAt: string; status?: ScheduledPost['status']; topicId?: number; createdBy?: string; image?: File }) => {
    const formData = new FormData();
    formData.append('title', payload.title);
    formData.append('content', payload.content);
    formData.append('channelIds', JSON.stringify(payload.channelIds || []));
    // Преобразуем datetime-local в ISO формат
    const scheduledAtISO = new Date(payload.scheduledAt).toISOString();
    formData.append('scheduledAt', scheduledAtISO);
    if (payload.status) formData.append('status', payload.status);
    if (payload.topicId) formData.append('topicId', String(payload.topicId));
    if (payload.createdBy) formData.append('createdBy', payload.createdBy);
    if (payload.image) formData.append('image', payload.image);
    // Не устанавливаем Content-Type вручную - браузер сделает это сам с boundary
    return apiClient.post<ScheduledPost>('/pr/scheduled-posts', formData);
  },
  updateScheduledPost: (id: number, payload: Partial<Pick<ScheduledPost, 'title' | 'content' | 'channelIds' | 'scheduledAt' | 'status' | 'topicId'>> & { image?: File; removeImage?: boolean }) => {
    const formData = new FormData();
    if (payload.title !== undefined) formData.append('title', payload.title);
    if (payload.content !== undefined) formData.append('content', payload.content);
    if (payload.channelIds !== undefined) formData.append('channelIds', JSON.stringify(payload.channelIds));
    if (payload.scheduledAt !== undefined) {
      // Преобразуем datetime-local в ISO формат
      const scheduledAtISO = new Date(payload.scheduledAt).toISOString();
      formData.append('scheduledAt', scheduledAtISO);
    }
    if (payload.status !== undefined) formData.append('status', payload.status);
    if (payload.topicId !== undefined) formData.append('topicId', payload.topicId !== null ? String(payload.topicId) : '');
    if (payload.image) formData.append('image', payload.image);
    if (payload.removeImage) formData.append('removeImage', 'true');
    // Не устанавливаем Content-Type вручную - браузер сделает это сам с boundary
    return apiClient.put<ScheduledPost>(`/pr/scheduled-posts/${id}`, formData);
  },
  approveScheduledPost: (id: number, payload?: { approvedBy?: string }) =>
    apiClient.post<ScheduledPost>(`/pr/scheduled-posts/${id}/approve`, payload || {}),
  rejectScheduledPost: (id: number, payload: { rejectionReason?: string; approvedBy?: string }) =>
    apiClient.post<ScheduledPost>(`/pr/scheduled-posts/${id}/reject`, payload),
  sendScheduledPostToEdit: (id: number, payload: { editedContent: string; approvedBy?: string }) =>
    apiClient.post<ScheduledPost>(`/pr/scheduled-posts/${id}/send-to-edit`, payload),
  deleteScheduledPost: (id: number) => apiClient.delete<{ success: boolean }>(`/pr/scheduled-posts/${id}`),
};


