import { Fragment, type ChangeEvent as ReactChangeEvent, type ClipboardEvent as ReactClipboardEvent, type Dispatch, type DragEvent as ReactDragEvent, type FormEvent as ReactFormEvent, type KeyboardEvent as ReactKeyboardEvent, type MouseEvent as ReactMouseEvent, type PointerEvent as ReactPointerEvent, type ReactNode, type SetStateAction, type WheelEvent as ReactWheelEvent, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import './styles.css';
import { Link, NavLink, Route, Routes, useLocation, useNavigate, useNavigationType, useParams, useSearchParams } from 'react-router-dom';
import { ROUTES, listingPath, mapPath, negotiationPath, companiesPath, adminTabPath, ADMIN_PATH_TO_TAB, type AdminTabKey } from './routes';
import { ProtectedRoute } from './ProtectedRoute';
import { apiFetch, useAuth } from './auth';
import type { AuthUser, ContactPreference, PreferredLanguage } from './auth';
import { useNotifications, toDisplayNotifications } from './notifications';
import type { ServerNotification, DisplayNotification } from './notifications';
import { getManualTranslation } from './i18nOverrides';
import { AdminLeadsView, AdminCampaignsView, AdminMailConfigView } from './admin/marketing';

const heroImage = new URL('./assets/hero-interior.png', import.meta.url).href;
const cityImage = new URL('./assets/city-panorama.png', import.meta.url).href;
const loginRoomImage = new URL('./assets/login-room.png', import.meta.url).href;
const SMS_VERIFICATION_CODE = '123456';

// Podswietlenie aktywnej pozycji menu wyznacza NavLink na podstawie adresu.
const navLinkClass = ({ isActive }: { isActive: boolean }) => (isActive ? 'active' : '');

type MessageThreadListing = {
  listingId: number;
  title: string;
  price: number;
  city: string;
  district: string | null;
  area: number;
  rooms: number;
  floor: string | null;
  buildingFloors: string | null;
  thumbnail: string | null;
};

type MessageThreadTarget = {
  id: string;
  name: string;
  status: string;
  listing: MessageThreadListing;
};

export type ApiOfferType = 'SALE' | 'RENT';
export type ApiPropertyType = 'APARTMENT' | 'HOUSE';
export type ApiListingStatus = 'PENDING' | 'APPROVED' | 'REJECTED' | 'PAUSED';

// Lekka reprezentacja z list (Kupuje/Wynajmuje, moje ogloszenia) - bez pelnej galerii.
export type ApiListingSummary = {
  id: number;
  title: string;
  description: string;
  offerType: ApiOfferType;
  propertyType: ApiPropertyType;
  city: string;
  district: string | null;
  address: string | null;
  price: number;
  area: number;
  rooms: number;
  floor: string | null;
  buildingFloors: string | null;
  market: string | null;
  yearBuilt: number | null;
  coverPhoto: string | null;
  ownerEmail: string | null;
  status: ApiListingStatus;
  viewsCount: number;
  createdAt: string;
};

// Pelne ogloszenie dla widoku szczegolow.
export type ApiListingDetail = {
  id: number;
  title: string;
  description: string;
  offerType: ApiOfferType;
  propertyType: ApiPropertyType;
  city: string;
  district: string | null;
  street: string | null;
  building: string | null;
  address: string | null;
  price: number;
  area: number;
  rooms: number;
  floor: string | null;
  buildingFloors: string | null;
  market: string | null;
  yearBuilt: number | null;
  condition: string | null;
  ownership: string | null;
  contactName: string | null;
  contactPhone: string | null;
  contactEmail: string | null;
  rentExtra: number | null;
  availableFrom: string | null;
  furnishing: string | null;
  buildingType: string | null;
  buildingMaterial: string | null;
  windows: string | null;
  exposure: string | null;
  roomHeight: string | null;
  lat: number | null;
  lng: number | null;
  media: string[];
  amenities: string[];
  photos: string[];
  virtualTourUrl: string | null;
  ownerEmail: string | null;
  status: ApiListingStatus;
  createdAt: string;
  viewsCount?: number;
};

type LiveListingSummary = ApiListingSummary & {
  lat: number | null;
  lng: number | null;
};

const LIVE_LISTINGS_REFRESH_MS = 15000;

type NotificationCategory = 'listing' | 'messages' | 'system';

type UserNotification = {
  id: string;
  title: string;
  description: string;
  timeLabel: string;
  dayLabel: 'Dzisiaj' | 'Wczoraj';
  category: NotificationCategory;
  icon: string;
  unread?: boolean;
  action?: 'arrow';
};

const userNotifications: UserNotification[] = [
  {
    id: 'new-message',
    title: 'Nowa wiadomość',
    description: 'Masz nową wiadomość od użytkownika Anna Nowak',
    timeLabel: '11:24',
    dayLabel: 'Dzisiaj',
    category: 'messages',
    icon: 'message',
    unread: true,
  },
  {
    id: 'saved-listing',
    title: 'Ogłoszenie zapisane',
    description: 'Mieszkanie 2-pokojowe na Mokotowie zostało zapisane do ulubionych.',
    timeLabel: '10:15',
    dayLabel: 'Dzisiaj',
    category: 'listing',
    icon: 'heart',
    unread: true,
  },
  {
    id: 'matching-listings',
    title: 'Nowe ogłoszenia dopasowane do Ciebie',
    description: 'Zobacz 3 nowe oferty mieszkań, które mogą Cię zainteresować.',
    timeLabel: '09:43',
    dayLabel: 'Dzisiaj',
    category: 'listing',
    icon: 'bell',
    action: 'arrow',
  },
  {
    id: 'price-drop',
    title: 'Zmiana ceny',
    description: 'Cena mieszkania „Apartament z widokiem” spadła o 20 000 zł.',
    timeLabel: '09:12',
    dayLabel: 'Dzisiaj',
    category: 'listing',
    icon: 'coin',
    action: 'arrow',
  },
  {
    id: 'phone-verified',
    title: 'Numer telefonu zweryfikowany',
    description: 'Twój numer telefonu został pomyślnie zweryfikowany.',
    timeLabel: '08:30',
    dayLabel: 'Dzisiaj',
    category: 'system',
    icon: 'check',
    unread: true,
  },
  {
    id: 'support-reply',
    title: 'Wiadomość od obsługi Mieszko',
    description: 'Dziękujemy za kontakt. Odpowiemy najszybciej jak to możliwe.',
    timeLabel: 'Wczoraj, 18:20',
    dayLabel: 'Wczoraj',
    category: 'messages',
    icon: 'mail',
    action: 'arrow',
  },
  {
    id: 'listing-views',
    title: 'Twoje ogłoszenie ma nowe wyświetlenia',
    description: 'Twoje ogłoszenie „Kawalerka w centrum” zostało wyświetlone 23 razy.',
    timeLabel: 'Wczoraj, 16:05',
    dayLabel: 'Wczoraj',
    category: 'listing',
    icon: 'eye',
    unread: true,
  },
  {
    id: 'new-review',
    title: 'Nowa opinia',
    description: 'Katarzyna Zielińska wystawiła Ci pozytywną opinię.',
    timeLabel: 'Wczoraj, 12:47',
    dayLabel: 'Wczoraj',
    category: 'system',
    icon: 'star',
    action: 'arrow',
  },
];

const meetingsStorageKey = 'polskalokalnie-account-meetings';
const listingsStorageKey = 'polskalokalnie-account-listings';

function getMeetingReminderNotifications(referenceDate = new Date()): UserNotification[] {
  if (typeof window === 'undefined') {
    return [];
  }

  type StoredMeeting = {
    id: string;
    title: string;
    location: string;
    dateKey: string;
    start: string;
    status?: string;
  };

  try {
    const raw = window.localStorage.getItem(meetingsStorageKey);
    if (!raw) {
      return [];
    }

    const parsed = JSON.parse(raw);
    if (!Array.isArray(parsed)) {
      return [];
    }

    const nowMs = referenceDate.getTime();
    const oneDayMs = 24 * 60 * 60 * 1000;

    return parsed
      .filter((item): item is StoredMeeting => (
        item
        && typeof item.id === 'string'
        && typeof item.title === 'string'
        && typeof item.location === 'string'
        && typeof item.dateKey === 'string'
        && typeof item.start === 'string'
      ))
      .filter((meeting) => (meeting.status ?? 'planned') === 'planned')
      .map((meeting) => {
        const meetingDateTime = new Date(`${meeting.dateKey}T${meeting.start}:00`);
        const meetingMs = meetingDateTime.getTime();
        const reminderStartMs = meetingMs - oneDayMs;
        const isInReminderWindow = nowMs >= reminderStartMs && nowMs < meetingMs;
        return { meeting, meetingDateTime, isInReminderWindow };
      })
      .filter((row) => row.isInReminderWindow)
      .sort((left, right) => left.meetingDateTime.getTime() - right.meetingDateTime.getTime())
      .map(({ meeting, meetingDateTime }) => ({
        id: `meeting-reminder-${meeting.id}`,
        title: 'Przypomnienie o spotkaniu',
        description: `${meeting.title} o ${meeting.start} (${meeting.location}).`,
        timeLabel: meetingDateTime.toLocaleTimeString('pl-PL', { hour: '2-digit', minute: '2-digit' }),
        dayLabel: 'Dzisiaj' as const,
        category: 'system' as const,
        icon: 'calendar',
        unread: true,
        action: 'arrow' as const,
      }));
  } catch {
    return [];
  }
}

function getListingResumeNotifications(referenceDate = new Date()): UserNotification[] {
  if (typeof window === 'undefined') {
    return [];
  }

  type StoredListing = {
    id: string;
    title: string;
    status?: string;
    pausedUntil?: string | null;
  };

  try {
    const raw = window.localStorage.getItem(listingsStorageKey);
    if (!raw) {
      return [];
    }

    const parsed = JSON.parse(raw);
    if (!Array.isArray(parsed)) {
      return [];
    }

    const nowMs = referenceDate.getTime();
    const oneDayMs = 24 * 60 * 60 * 1000;

    return parsed
      .filter((item): item is StoredListing => (
        item
        && typeof item.id === 'string'
        && typeof item.title === 'string'
        && typeof item.status === 'string'
      ))
      .filter((item) => item.status === 'paused' && typeof item.pausedUntil === 'string' && item.pausedUntil.length > 0)
      .map((item) => {
        const resumeDateTime = new Date(item.pausedUntil as string);
        const resumeMs = resumeDateTime.getTime();
        const reminderStartMs = resumeMs - oneDayMs;
        const isInReminderWindow = nowMs >= reminderStartMs && nowMs < resumeMs;
        return { item, resumeDateTime, isInReminderWindow };
      })
      .filter((row) => row.isInReminderWindow)
      .sort((left, right) => left.resumeDateTime.getTime() - right.resumeDateTime.getTime())
      .map(({ item, resumeDateTime }) => ({
        id: `listing-resume-${item.id}`,
        title: 'Jutro ogłoszenie zostanie przywrócone',
        description: `${item.title} zostanie automatycznie wznowione ${resumeDateTime.toLocaleDateString('pl-PL')} o ${resumeDateTime.toLocaleTimeString('pl-PL', { hour: '2-digit', minute: '2-digit' })}.`,
        timeLabel: resumeDateTime.toLocaleTimeString('pl-PL', { hour: '2-digit', minute: '2-digit' }),
        dayLabel: 'Dzisiaj' as const,
        category: 'listing' as const,
        icon: 'bell',
        unread: true,
        action: 'arrow' as const,
      }));
  } catch {
    return [];
  }
}

function getAllNotifications(referenceDate = new Date()): UserNotification[] {
  return [
    ...getListingResumeNotifications(referenceDate),
    ...getMeetingReminderNotifications(referenceDate),
    ...userNotifications,
  ];
}

type FavoriteTab = 'buy' | 'rent';

type FavoriteListing = {
  id: string;
  listingId: number;
  mode: FavoriteTab;
  image: string;
  price: string;
  title: string;
  location: string;
  area: string;
  rooms: string;
  floor: string;
  year: string;
  added: string;
  photos: number;
  badge: string;
};

type FavoritePriceAlertEvent = {
  id: string;
  listingId: number;
  title: string;
  location: string;
  area: string;
  offerType?: ApiOfferType;
  previousPrice: number;
  currentPrice: number;
  changedAt: string;
};

type CityPriceAlertSnapshot = {
  dayKey: string;
  perM2: number;
  updatedAt: string;
};

type CityPriceAlertEvent = {
  id: string;
  subscriptionKey: string;
  label: string;
  city: string;
  region: string;
  propertyType: HistoryPropertyType;
  marketType: HistoryMarketType;
  previousPerM2: number;
  currentPerM2: number;
  changedAt: string;
};

type CityPriceAlertSubscription = {
  key: string;
  label: string;
  city: string;
  region: string;
  propertyType: HistoryPropertyType;
  marketType: HistoryMarketType;
  createdAt: string;
};

type ListingPriceAlertSubscription = {
  listingId: number;
  title: string;
  location: string;
  offerType: ApiOfferType;
  createdAt: string;
};

type SavedSearchMode = 'buy' | 'rent';

type SavedSearchRecord = {
  id: string;
  mode: SavedSearchMode;
  city: string;
  propertyType: string;
  priceMin: number | null;
  priceMax: number | null;
  areaMin: number | null;
  areaMax: number | null;
  rooms: number | null;
  wtorny: boolean;
  pierwotny: boolean;
  noFeeOnly: boolean;
  more: Record<string, string>;
  createdAt: string;
  updatedAt: string;
};

const FAVORITE_PRICE_ALERTS_STORAGE_SUFFIX = 'favorite-price-alert-events';
const FAVORITE_PRICE_SNAPSHOTS_STORAGE_SUFFIX = 'favorite-price-snapshots';
const FAVORITE_LISTINGS_STORAGE_SUFFIX = 'favorite-listings';
const FAVORITES_PREFERRED_TAB_STORAGE_SUFFIX = 'favorites-preferred-tab';
const CITY_PRICE_ALERTS_STORAGE_SUFFIX = 'city-price-alert-subscriptions';
const LISTING_PRICE_ALERTS_STORAGE_SUFFIX = 'listing-price-alert-subscriptions';
const CITY_PRICE_ALERT_SNAPSHOTS_STORAGE_SUFFIX = 'city-price-alert-snapshots';
const CITY_PRICE_ALERT_EVENTS_STORAGE_SUFFIX = 'city-price-alert-events';
const CITY_PRICE_ALERT_LAST_RUN_STORAGE_SUFFIX = 'city-price-alert-last-run';
const SAVED_SEARCHES_STORAGE_SUFFIX = 'saved-searches';
const PRICE_ALERT_EVENTS_LIMIT = 40;
const SAVED_SEARCHES_LIMIT = 60;

function readUserJsonStorage<T>(user: AuthUser | null, suffix: string, fallback: T): T {
  if (typeof window === 'undefined') {
    return fallback;
  }
  try {
    const raw = window.localStorage.getItem(accountStorageKey(suffix, user));
    if (!raw) {
      return fallback;
    }
    return JSON.parse(raw) as T;
  } catch {
    return fallback;
  }
}

function writeUserJsonStorage<T>(user: AuthUser | null, suffix: string, value: T): void {
  if (typeof window === 'undefined') {
    return;
  }
  window.localStorage.setItem(accountStorageKey(suffix, user), JSON.stringify(value));
}

function mergePriceAlertEvents(
  incoming: FavoritePriceAlertEvent[],
  existing: FavoritePriceAlertEvent[],
): FavoritePriceAlertEvent[] {
  if (incoming.length === 0) {
    return existing;
  }

  const known = new Set(existing.map((item) => item.id));
  const uniqueIncoming = incoming.filter((item) => !known.has(item.id));
  if (uniqueIncoming.length === 0) {
    return existing;
  }

  return [...uniqueIncoming, ...existing]
    .sort((left, right) => new Date(right.changedAt).getTime() - new Date(left.changedAt).getTime())
    .slice(0, PRICE_ALERT_EVENTS_LIMIT);
}

function currentLocalDayKey(date = new Date()): string {
  const year = date.getFullYear();
  const month = `${date.getMonth() + 1}`.padStart(2, '0');
  const day = `${date.getDate()}`.padStart(2, '0');
  return `${year}-${month}-${day}`;
}

function cityAlertStorageKey(subscription: CityPriceAlertSubscription): string {
  return `${subscription.key}|${subscription.propertyType}|${subscription.marketType}`;
}

function cityAlertPropertyTypeToApi(propertyType: HistoryPropertyType): ApiPropertyType | null {
  if (propertyType === 'Mieszkanie') {
    return 'APARTMENT';
  }
  if (propertyType === 'Dom') {
    return 'HOUSE';
  }
  return null;
}

function cityAlertMarketFilter(marketType: HistoryMarketType): 'wtorny' | 'pierwotny' | null {
  if (marketType === 'Wtórny') {
    return 'wtorny';
  }
  if (marketType === 'Pierwotny') {
    return 'pierwotny';
  }
  return null;
}

function calculateCityAlertPerM2(subscription: CityPriceAlertSubscription, listings: ApiListingSummary[]): number {
  const targetPropertyType = cityAlertPropertyTypeToApi(subscription.propertyType);
  const targetMarket = cityAlertMarketFilter(subscription.marketType);
  const cityQuery = subscription.city.trim() || subscription.label.split(',')[0]?.trim() || '';

  const filtered = listings.filter((item) => {
    if (cityQuery && !cityStartsWith(item.city, cityQuery)) {
      return false;
    }
    if (targetPropertyType && item.propertyType !== targetPropertyType) {
      return false;
    }
    if (targetMarket && item.market !== targetMarket) {
      return false;
    }
    return item.area > 0 && item.price > 0;
  });

  if (filtered.length === 0) {
    const estimate = estimateHistoryLocationPrice(subscription.label);
    const fallback = Math.round(
      estimate.price
      * HISTORY_PROPERTY_MULTIPLIERS[subscription.propertyType]
      * HISTORY_MARKET_MULTIPLIERS[subscription.marketType],
    );
    return Math.max(1, fallback);
  }

  const averagePerM2 = filtered.reduce((sum, item) => sum + (item.price / item.area), 0) / filtered.length;
  return Math.max(1, Math.round(averagePerM2));
}

function mergeCityPriceAlertEvents(
  incoming: CityPriceAlertEvent[],
  existing: CityPriceAlertEvent[],
): CityPriceAlertEvent[] {
  if (incoming.length === 0) {
    return existing;
  }

  const known = new Set(existing.map((item) => item.id));
  const uniqueIncoming = incoming.filter((item) => !known.has(item.id));
  if (uniqueIncoming.length === 0) {
    return existing;
  }

  return [...uniqueIncoming, ...existing]
    .sort((left, right) => new Date(right.changedAt).getTime() - new Date(left.changedAt).getTime())
    .slice(0, PRICE_ALERT_EVENTS_LIMIT);
}

const favoriteListings: FavoriteListing[] = [];

function favoriteListingKey(listingId: number): string {
  return `listing-${listingId}`;
}

function favoriteTabFromOfferType(offerType: ApiOfferType): FavoriteTab {
  return offerType === 'RENT' ? 'rent' : 'buy';
}

function toFavoriteListingFromSummary(listing: ApiListingSummary): FavoriteListing {
  const mode = favoriteTabFromOfferType(listing.offerType);
  const price = listing.offerType === 'RENT' ? `${formatPln(listing.price)} / mies.` : formatPln(listing.price);
  const location = [listing.city, listing.district].filter(Boolean).join(', ');
  const floor = listingFloorText(listing.floor, listing.buildingFloors);

  return {
    id: favoriteListingKey(listing.id),
    listingId: listing.id,
    mode,
    image: listing.coverPhoto ?? heroImage,
    price,
    title: listing.title,
    location: location || listing.city,
    area: `${`${listing.area}`.replace('.', ',')} m²`,
    rooms: listingRoomsText(listing.rooms),
    floor: floor ? `${floor} piętro` : 'Brak danych o piętrze',
    year: listing.yearBuilt ? `${listing.yearBuilt} rok` : 'Brak danych o roku budowy',
    added: listingRelativeDate(listing.createdAt) || 'Dodane niedawno',
    photos: 1,
    badge: mode === 'rent' ? 'Na wynajem' : 'Na sprzedaż',
  };
}

function toFavoriteListing(listing: ApiListingDetail): FavoriteListing {
  const mode = favoriteTabFromOfferType(listing.offerType);
  const price = listing.offerType === 'RENT' ? `${formatPln(listing.price)} / mies.` : formatPln(listing.price);
  const location = [listing.city, listing.district].filter(Boolean).join(', ');
  const floor = listingFloorText(listing.floor, listing.buildingFloors);

  return {
    id: favoriteListingKey(listing.id),
    listingId: listing.id,
    mode,
    image: listing.photos[0] ?? heroImage,
    price,
    title: listing.title,
    location: location || listing.city,
    area: `${`${listing.area}`.replace('.', ',')} m²`,
    rooms: listingRoomsText(listing.rooms),
    floor: floor ? `${floor} piętro` : 'Brak danych o piętrze',
    year: listing.yearBuilt ? `${listing.yearBuilt} rok` : 'Brak danych o roku budowy',
    added: listingRelativeDate(listing.createdAt) || 'Dodane niedawno',
    photos: Math.max(listing.photos.length, 1),
    badge: mode === 'rent' ? 'Na wynajem' : 'Na sprzedaż',
  };
}

function buildMessageThreadTargetFromListing(listing: ApiListingDetail, user: AuthUser | null): MessageThreadTarget {
  const sellerName = listing.contactName?.trim() || 'Administracja Mieszko';
  const isOwner = Boolean(user && listing.ownerEmail && user.email.toLowerCase() === listing.ownerEmail.toLowerCase());
  const sellerRole = isOwner ? 'To Twoje ogłoszenie' : 'Oficjalny kontakt zespołem Mieszko';
  const threadIdBase = listing.ownerEmail?.trim() || sellerName;
  const threadId = `listing-${listing.id}-${threadIdBase.toLowerCase().replace(/[^a-z0-9]+/g, '-')}`;

  return {
    id: threadId,
    name: sellerName,
    status: sellerRole,
    listing: {
      listingId: listing.id,
      title: listing.title,
      price: listing.price,
      city: listing.city,
      district: listing.district,
      area: listing.area,
      rooms: listing.rooms,
      floor: listing.floor,
      buildingFloors: listing.buildingFloors,
      thumbnail: listing.photos[0] ?? heroImage,
    },
  };
}

const benefits = [
  {
    icon: 'coin',
    title: '0 zł',
    text: 'za ogłoszenie',
  },
  {
    icon: 'chart',
    title: 'Tysiące kupujących',
    text: 'każdego dnia',
  },
  {
    icon: 'shuffle',
    title: 'AI dopasowuje',
    text: 'kupujących',
  },
  {
    icon: 'shield',
    title: 'Pełna widoczność',
    text: 'w całej Polsce',
  },
];

const buyQuickFilters = [
  {
    icon: 'flame',
    title: 'Okazje cenowe',
    text: 'Najlepsze oferty',
  },
  {
    icon: 'shield',
    title: 'Bez pośredników',
    text: 'Oferty prywatne',
  },
  {
    icon: 'spark',
    title: 'Nowe dzisiaj',
    text: 'Dodane w ciągu 24h',
  },
];

const insightCards = [
  {
    icon: 'gear',
    title: 'AI wycena mieszkania',
    text: 'Sprawdź ile warte jest Twoje mieszkanie w 2 minuty',
    action: 'Wycena za darmo',
    path: ROUTES.valuation,
  },
  {
    icon: 'trend',
    title: 'Historia cen',
    text: 'Sprawdź jak zmieniały się ceny w okolicy',
    action: 'Zobacz wykresy',
    path: ROUTES.priceHistory,
  },
  {
    icon: 'house',
    title: 'Ranking dzielnic',
    text: 'Porównaj dzielnice pod kątem życia i inwestycji',
    action: 'Zobacz ranking',
    path: ROUTES.districtRanking,
  },
  {
    icon: 'coin',
    title: 'Ile możesz utargować?',
    text: 'AI podpowie realistyczną cenę transakcyjną',
    action: 'Sprawdź teraz',
    path: ROUTES.negotiation,
  },
];

type DistrictRankingMetric = 'overall' | 'price' | 'popularity' | 'rating';
type DistrictPropertyType = 'flat' | 'house' | 'commercial';
type DistrictMarketType = 'all' | 'secondary' | 'primary';
type DistrictPeriod = '12' | '6' | '2026';

type DistrictRankingSource = {
  district: string;
  city: keyof typeof VALUATION_PRICE_PER_SQM_BY_CITY;
  lat: number;
  lng: number;
  price: number;
  monthlyChange: number;
  demand: number;
  liquidity: number;
  residentScore: number;
  investmentScore: number;
};

const districtRankingSources: DistrictRankingSource[] = [
  { district: 'Młyniska', city: 'Gdańsk', lat: 54.3792, lng: 18.6568, price: 20842, monthlyChange: 1.6, demand: 82, liquidity: 76, residentScore: 72, investmentScore: 89 },
  { district: 'Karlikowo', city: 'Sopot', lat: 54.4411, lng: 18.5652, price: 20678, monthlyChange: 4.46, demand: 91, liquidity: 82, residentScore: 88, investmentScore: 95 },
  { district: 'Śródmieście', city: 'Warszawa', lat: 52.235, lng: 21.0122, price: 20625, monthlyChange: 2.62, demand: 98, liquidity: 86, residentScore: 82, investmentScore: 98 },
  { district: 'Zacisze - Zalesie - Szczytniki', city: 'Wrocław', lat: 51.1133, lng: 17.098, price: 20475, monthlyChange: 2.07, demand: 78, liquidity: 74, residentScore: 95, investmentScore: 86 },
  { district: 'Orłowo', city: 'Gdynia', lat: 54.4793, lng: 18.5588, price: 19874, monthlyChange: 1.68, demand: 85, liquidity: 82, residentScore: 96, investmentScore: 88 },
  { district: 'Dolny Sopot', city: 'Sopot', lat: 54.445, lng: 18.5683, price: 19544, monthlyChange: 4.5, demand: 93, liquidity: 79, residentScore: 95, investmentScore: 92 },
  { district: 'Świniary', city: 'Wrocław', lat: 51.1678, lng: 16.9355, price: 19170, monthlyChange: 2.07, demand: 62, liquidity: 60, residentScore: 73, investmentScore: 63 },
  { district: 'Żoliborz', city: 'Warszawa', lat: 52.2682, lng: 20.9858, price: 18782, monthlyChange: 8, demand: 91, liquidity: 89, residentScore: 94, investmentScore: 90 },
  { district: 'Dzielnica I Stare Miasto', city: 'Kraków', lat: 50.0619, lng: 19.9373, price: 18685, monthlyChange: 3.11, demand: 96, liquidity: 68, residentScore: 86, investmentScore: 94 },
  { district: 'Śródmieście', city: 'Gdańsk', lat: 54.3519, lng: 18.6466, price: 18132, monthlyChange: 1.6, demand: 95, liquidity: 86, residentScore: 84, investmentScore: 92 },
  { district: 'Dzielnica VII Zwierzyniec', city: 'Kraków', lat: 50.0562, lng: 19.8985, price: 18087, monthlyChange: 3.11, demand: 84, liquidity: 78, residentScore: 96, investmentScore: 86 },
  { district: 'Wola', city: 'Warszawa', lat: 52.2338, lng: 20.9616, price: 17985, monthlyChange: 2.62, demand: 97, liquidity: 92, residentScore: 80, investmentScore: 97 },
  { district: 'Górny Sopot', city: 'Sopot', lat: 54.4378, lng: 18.5492, price: 17208, monthlyChange: 4.5, demand: 82, liquidity: 74, residentScore: 88, investmentScore: 81 },
  { district: 'Letnica', city: 'Gdańsk', lat: 54.3962, lng: 18.6386, price: 16813, monthlyChange: 1.6, demand: 88, liquidity: 84, residentScore: 76, investmentScore: 93 },
  { district: 'Dzielnica II Grzegórzki', city: 'Kraków', lat: 50.0606, lng: 19.9688, price: 16699, monthlyChange: 3.11, demand: 87, liquidity: 83, residentScore: 86, investmentScore: 90 },
  { district: 'Kamienna Góra', city: 'Gdynia', lat: 54.5066, lng: 18.5487, price: 16595, monthlyChange: 3.35, demand: 76, liquidity: 72, residentScore: 93, investmentScore: 78 },
  { district: 'Świemirowo', city: 'Sopot', lat: 54.4302, lng: 18.5451, price: 16530, monthlyChange: 12.85, demand: 74, liquidity: 62, residentScore: 81, investmentScore: 83 },
  { district: 'Śródmieście', city: 'Gdynia', lat: 54.5211, lng: 18.5392, price: 16269, monthlyChange: 3.35, demand: 94, liquidity: 80, residentScore: 84, investmentScore: 82 },
  { district: 'Strachocin - Swojczyce - Wojnów', city: 'Wrocław', lat: 51.1152, lng: 17.1477, price: 13669, monthlyChange: 2.07, demand: 70, liquidity: 72, residentScore: 87, investmentScore: 75 },
  { district: 'Ławica', city: 'Poznań', lat: 52.4229, lng: 16.7921, price: 13302, monthlyChange: 1.53, demand: 72, liquidity: 70, residentScore: 88, investmentScore: 74 },
];

const DISTRICT_PROPERTY_MULTIPLIERS: Record<DistrictPropertyType, number> = {
  flat: 1,
  house: 0.86,
  commercial: 1.12,
};

const DISTRICT_MARKET_MULTIPLIERS: Record<DistrictMarketType, number> = {
  all: 1,
  secondary: 0.97,
  primary: 1.08,
};

const DISTRICT_PERIOD_MULTIPLIERS: Record<DistrictPeriod, number> = {
  '12': 1,
  '6': 0.58,
  '2026': 0.46,
};

const DISTRICT_POPULARITY_PERIOD_ADJUSTMENT: Record<DistrictPeriod, number> = {
  '12': 0,
  '6': -2.5,
  '2026': 1.5,
};

const DISTRICT_RANKING_LABELS: Record<DistrictRankingMetric, string> = {
  overall: 'Ogólny ranking',
  price: 'Średnia cena za m²',
  popularity: 'Popularność',
  rating: 'Ocena mieszkańców',
};

function districtModelRow(source: DistrictRankingSource, propertyType: DistrictPropertyType, marketType: DistrictMarketType, period: DistrictPeriod) {
  const price = Math.round((source.price * DISTRICT_PROPERTY_MULTIPLIERS[propertyType] * DISTRICT_MARKET_MULTIPLIERS[marketType]) / 10) * 10;
  const cityBenchmark = VALUATION_PRICE_PER_SQM_BY_CITY[source.city];
  const pricePremiumVsCity = cityBenchmark > 0 ? ((price - cityBenchmark) / cityBenchmark) * 100 : 0;
  const marketHeatIndex = clampNumber(50 + source.monthlyChange * 5.2 + pricePremiumVsCity * 0.44 + source.liquidity * 0.12, 35, 100);
  const popularity = Math.round(clampNumber(
    source.demand * 0.44 + source.liquidity * 0.31 + marketHeatIndex * 0.25 + DISTRICT_POPULARITY_PERIOD_ADJUSTMENT[period],
    40,
    99,
  ));
  const rating = Number((source.residentScore / 20).toFixed(1));
  const affordability = clampNumber(100 - ((price - 6500) / 17000) * 48, 45, 100);
  const changeValue = clampNumber((source.monthlyChange * 2.2 + source.demand * 0.025 + source.investmentScore * 0.018 + (marketType === 'primary' ? 0.8 : marketType === 'secondary' ? -0.2 : 0)) * DISTRICT_PERIOD_MULTIPLIERS[period], 1.2, 13.8);
  const score = Math.round(clampNumber(popularity * 0.28 + source.investmentScore * 0.27 + source.residentScore * 0.25 + affordability * 0.12 + source.liquidity * 0.08, 45, 99));

  return {
    ...source,
    price,
    changeValue,
    change: `+${changeValue.toFixed(1).replace('.', ',')}%`,
    popularity,
    rating,
    ratingText: rating.toFixed(1).replace('.', ','),
    score,
  };
}

const listings = [
  {
    location: 'Kraków, Zabłocie',
    meta: 'Mieszkanie · 3 pokoje · 62 m²',
    price: '749 000 zł',
    perMeter: '12 081 zł/m²',
    badge: 'Nowe',
  },
  {
    location: 'Warszawa, Mokotów',
    meta: 'Mieszkanie · 2 pokoje · 45 m²',
    price: '679 000 zł',
    perMeter: '15 089 zł/m²',
    badge: 'Premium',
  },
  {
    location: 'Wrocław, Kępa Mieszczańska',
    meta: 'Mieszkanie · 4 pokoje · 78 m²',
    price: '899 000 zł',
    perMeter: '11 526 zł/m²',
    badge: 'Nowe',
  },
  {
    location: 'Gdańsk, Wrzeszcz',
    meta: 'Mieszkanie · 3 pokoje · 56 m²',
    price: '619 000 zł',
    perMeter: '11 054 zł/m²',
    badge: '',
  },
];

const cities = [
  { name: 'Warszawa', count: '12 582 ogłoszenia', position: '48% 55%' },
  { name: 'Kraków', count: '8 150 ogłoszeń', position: '54% 54%' },
  { name: 'Wrocław', count: '6 843 ogłoszenia', position: '42% 58%' },
  { name: 'Gdańsk', count: '5 231 ogłoszeń', position: '62% 52%' },
];

const rentQuickFilters = [
  {
    icon: 'sun',
    title: 'Nowe dzisiaj',
    text: 'Dodane w ciągu 24h',
  },
  {
    icon: 'student',
    title: 'Dla studentów',
    text: 'Blisko uczelni',
  },
  {
    icon: 'paw',
    title: 'Zwierzęta mile widziane',
    text: 'Przyjazne zwierzętom',
  },
  {
    icon: 'car',
    title: 'Z miejscem parkingowym',
    text: 'Parking dostępny',
  },
];

type BuyOffer = {
  title: string;
  propertyType: PropertySearchType;
  city: string;
  district: string;
  rooms: number;
  area: number;
  priceValue: number;
  market: 'wtorny' | 'pierwotny';
  year: number;
  date: string;
  dateOrder: number;
  score: string;
  saving: string;
  tags: string[];
  position: string;
  image: 'hero' | 'city';
};

type PropertySearchType = 'Mieszkanie' | 'Dom' | 'Działka' | 'Lokal użytkowy' | 'Pokój';

const buyListings: BuyOffer[] = [
  {
    title: 'Mieszkanie 3-pokojowe',
    propertyType: 'Mieszkanie',
    city: 'Warszawa',
    district: 'Warszawa, Mokotów, Stegny',
    rooms: 3,
    area: 62,
    priceValue: 749000,
    market: 'wtorny',
    year: 1980,
    date: 'Dodane dzisiaj',
    dateOrder: 0,
    score: 'Ocena oferty 8.7/10',
    saving: 'Możliwe do negocjacji: ok. 35 000 zł',
    tags: ['Okazja', 'Bez prowizji'],
    position: '47% 58%',
    image: 'hero',
  },
  {
    title: 'Mieszkanie 2-pokojowe',
    propertyType: 'Mieszkanie',
    city: 'Warszawa',
    district: 'Warszawa, Praga-Południe, Grochów',
    rooms: 2,
    area: 45,
    priceValue: 699000,
    market: 'pierwotny',
    year: 2023,
    date: 'Dodane dzisiaj',
    dateOrder: 0,
    score: 'Ocena oferty 8.2/10',
    saving: 'Możliwe do negocjacji: ok. 20 000 zł',
    tags: ['Nowe', 'Bez prowizji'],
    position: '86% 55%',
    image: 'city',
  },
  {
    title: 'Mieszkanie 3-pokojowe',
    propertyType: 'Mieszkanie',
    city: 'Warszawa',
    district: 'Warszawa, Bemowo, Górczewska',
    rooms: 3,
    area: 55,
    priceValue: 699000,
    market: 'wtorny',
    year: 2010,
    date: 'Dodane wczoraj',
    dateOrder: 1,
    score: 'Ocena oferty 7.9/10',
    saving: 'Możliwe do negocjacji: ok. 25 000 zł',
    tags: ['Bez prowizji'],
    position: '58% 62%',
    image: 'hero',
  },
  {
    title: 'Dom 4-pokojowy',
    propertyType: 'Dom',
    city: 'Warszawa',
    district: 'Warszawa, Bielany, Chomiczówka',
    rooms: 4,
    area: 72,
    priceValue: 789000,
    market: 'wtorny',
    year: 1995,
    date: 'Dodane 2 dni temu',
    dateOrder: 2,
    score: 'Ocena oferty 8.1/10',
    saving: 'Cena niższa od podobnych ofert',
    tags: ['Okazja', 'Bez prowizji'],
    position: '33% 58%',
    image: 'hero',
  },
  {
    title: 'Mieszkanie 2-pokojowe',
    propertyType: 'Mieszkanie',
    city: 'Kraków',
    district: 'Kraków, Podgórze, Zabłocie',
    rooms: 2,
    area: 48,
    priceValue: 679000,
    market: 'pierwotny',
    year: 2024,
    date: 'Dodane dzisiaj',
    dateOrder: 0,
    score: 'Ocena oferty 8.5/10',
    saving: 'Cena rynkowa dla lokalizacji',
    tags: ['Nowe', 'Bez prowizji'],
    position: '52% 55%',
    image: 'city',
  },
  {
    title: 'Mieszkanie 3-pokojowe',
    propertyType: 'Mieszkanie',
    city: 'Kraków',
    district: 'Kraków, Krowodrza, Bronowice',
    rooms: 3,
    area: 64,
    priceValue: 845000,
    market: 'wtorny',
    year: 2008,
    date: 'Dodane wczoraj',
    dateOrder: 1,
    score: 'Ocena oferty 8.0/10',
    saving: 'Możliwe do negocjacji: ok. 30 000 zł',
    tags: ['Bez prowizji'],
    position: '44% 60%',
    image: 'hero',
  },
  {
    title: 'Dom z ogrodem',
    propertyType: 'Dom',
    city: 'Wrocław',
    district: 'Wrocław, Krzyki, Borek',
    rooms: 4,
    area: 78,
    priceValue: 899000,
    market: 'wtorny',
    year: 2001,
    date: 'Dodane dzisiaj',
    dateOrder: 0,
    score: 'Ocena oferty 8.3/10',
    saving: 'Możliwe do negocjacji: ok. 40 000 zł',
    tags: ['Okazja', 'Bez prowizji'],
    position: '60% 50%',
    image: 'hero',
  },
  {
    title: 'Mieszkanie 2-pokojowe',
    propertyType: 'Mieszkanie',
    city: 'Wrocław',
    district: 'Wrocław, Stare Miasto, Kępa Mieszczańska',
    rooms: 2,
    area: 41,
    priceValue: 569000,
    market: 'pierwotny',
    year: 2025,
    date: 'Dodane 2 dni temu',
    dateOrder: 2,
    score: 'Ocena oferty 8.6/10',
    saving: 'Nowa inwestycja, cena deweloperska',
    tags: ['Nowe', 'Bez prowizji'],
    position: '38% 54%',
    image: 'city',
  },
  {
    title: 'Mieszkanie 3-pokojowe',
    propertyType: 'Mieszkanie',
    city: 'Gdańsk',
    district: 'Gdańsk, Wrzeszcz, Strzyża',
    rooms: 3,
    area: 58,
    priceValue: 749000,
    market: 'wtorny',
    year: 2015,
    date: 'Dodane dzisiaj',
    dateOrder: 0,
    score: 'Ocena oferty 8.4/10',
    saving: 'Cena niższa od podobnych ofert',
    tags: ['Okazja', 'Bez prowizji'],
    position: '66% 48%',
    image: 'hero',
  },
  {
    title: 'Pokój inwestycyjny',
    propertyType: 'Pokój',
    city: 'Gdańsk',
    district: 'Gdańsk, Śródmieście, Wyspa Spichrzów',
    rooms: 1,
    area: 29,
    priceValue: 459000,
    market: 'pierwotny',
    year: 2024,
    date: 'Dodane wczoraj',
    dateOrder: 1,
    score: 'Ocena oferty 8.1/10',
    saving: 'Idealne pod inwestycję',
    tags: ['Nowe', 'Bez prowizji'],
    position: '72% 52%',
    image: 'city',
  },
  {
    title: 'Działka budowlana',
    propertyType: 'Działka',
    city: 'Poznań',
    district: 'Poznań, Jeżyce, Sołacz',
    rooms: 3,
    area: 61,
    priceValue: 689000,
    market: 'wtorny',
    year: 2012,
    date: 'Dodane 3 dni temu',
    dateOrder: 3,
    score: 'Ocena oferty 7.8/10',
    saving: 'Możliwe do negocjacji: ok. 22 000 zł',
    tags: ['Bez prowizji'],
    position: '50% 58%',
    image: 'hero',
  },
  {
    title: 'Lokal użytkowy',
    propertyType: 'Lokal użytkowy',
    city: 'Łódź',
    district: 'Łódź, Śródmieście, Nowe Centrum',
    rooms: 2,
    area: 44,
    priceValue: 379000,
    market: 'wtorny',
    year: 2018,
    date: 'Dodane dzisiaj',
    dateOrder: 0,
    score: 'Ocena oferty 8.0/10',
    saving: 'Bardzo dobra cena za m²',
    tags: ['Okazja', 'Bez prowizji'],
    position: '54% 56%',
    image: 'city',
  },
];

const POLISH_CITIES = [
  'Warszawa', 'Kraków', 'Łódź', 'Wrocław', 'Poznań', 'Gdańsk', 'Szczecin', 'Bydgoszcz',
  'Lublin', 'Białystok', 'Katowice', 'Gdynia', 'Częstochowa', 'Radom', 'Sosnowiec',
  'Toruń', 'Kielce', 'Rzeszów', 'Gliwice', 'Zabrze', 'Olsztyn', 'Bielsko-Biała',
  'Bytom', 'Zielona Góra', 'Rybnik', 'Ruda Śląska', 'Opole', 'Tychy', 'Gorzów Wielkopolski',
  'Dąbrowa Górnicza', 'Elbląg', 'Płock', 'Wałbrzych', 'Włocławek', 'Tarnów', 'Chorzów',
  'Koszalin', 'Kalisz', 'Legnica', 'Grudziądz', 'Słupsk', 'Jaworzno', 'Jastrzębie-Zdrój',
  'Nowy Sącz', 'Jelenia Góra', 'Siedlce', 'Mysłowice', 'Konin', 'Piła', 'Piotrków Trybunalski',
  'Inowrocław', 'Lubin', 'Ostrów Wielkopolski', 'Suwałki', 'Stargard', 'Gniezno', 'Ostrowiec Świętokrzyski',
  'Siemianowice Śląskie', 'Głogów', 'Pabianice', 'Leszno', 'Żory', 'Zamość', 'Pruszków',
  'Łomża', 'Ełk', 'Tomaszów Mazowiecki', 'Chełm', 'Mielec', 'Kędzierzyn-Koźle', 'Przemyśl',
  'Stalowa Wola', 'Tczew', 'Biała Podlaska', 'Bełchatów', 'Świdnica', 'Będzin', 'Zgierz',
  'Piekary Śląskie', 'Racibórz', 'Legionowo', 'Ostrołęka', 'Świnoujście', 'Starachowice',
  'Zawiercie', 'Wejherowo', 'Puławy', 'Wodzisław Śląski', 'Skierniewice', 'Tarnowskie Góry',
  'Starogard Gdański', 'Rumia', 'Kutno', 'Nysa', 'Sieradz', 'Radomsko', 'Otwock', 'Skarżysko-Kamienna',
  'Dębica', 'Ciechanów', 'Kalisz', 'Mińsk Mazowiecki', 'Krosno', 'Piaseczno', 'Sopot',
  'Świętochłowice', 'Nowy Targ', 'Zakopane', 'Oświęcim', 'Bochnia', 'Brzeg', 'Malbork',
  'Kołobrzeg', 'Sanok', 'Jarosław', 'Lubliniec', 'Augustów', 'Władysławowo', 'Gostyń',
  'Wągrowiec', 'Żywiec', 'Cieszyn', 'Mława', 'Kraśnik', 'Świdnik', 'Grójec', 'Wieliczka',
  'Józefów', 'Marki', 'Ząbki', 'Łuków', 'Hrubieszów', 'Biłgoraj', 'Sandomierz', 'Police',
  'Knurów', 'Czeladź', 'Bolesławiec', 'Oleśnica', 'Kłodzko', 'Dzierżoniów', 'Nowa Sól',
  'Żary', 'Kościerzyna', 'Chojnice', 'Kwidzyn', 'Lębork', 'Iława', 'Giżycko', 'Działdowo',
  'Szczecinek', 'Wałcz', 'Białogard', 'Goleniów', 'Gryfino', 'Międzyrzecz', 'Krotoszyn',
  'Jarocin', 'Środa Wielkopolska', 'Turek', 'Koło', 'Września', 'Śrem', 'Rawicz', 'Kościan',
];

const ROOM_WORDS: Record<number, string> = { 1: 'pokój', 2: 'pokoje', 3: 'pokoje', 4: 'pokoje', 5: 'pokoi' };

const POPULAR_CITIES = ['Warszawa', 'Kraków', 'Wrocław', 'Gdańsk', 'Poznań', 'Łódź'];

const DEFAULT_RECENT = ['Warszawa Mokotów', 'Kraków Zabłocie', 'Wrocław Krzyki'];

type SortKey = 'default' | 'new' | 'old' | 'cheap' | 'expensive' | 'small' | 'big';

const SORT_OPTIONS: { value: SortKey; label: string }[] = [
  { value: 'default', label: 'Domyślnie' },
  { value: 'new', label: 'Data dodania: najnowsze' },
  { value: 'old', label: 'Data dodania: najstarsze' },
  { value: 'cheap', label: 'Cena: od najniższej' },
  { value: 'expensive', label: 'Cena: od najwyższej' },
  { value: 'small', label: 'Powierzchnia: od najmniejszej' },
  { value: 'big', label: 'Powierzchnia: od największej' },
];

const PRICE_PRESETS: { label: string; value: number }[] = [
  { label: 'do 500k', value: 500000 },
  { label: 'do 700k', value: 700000 },
  { label: 'do 1 mln', value: 1000000 },
  { label: 'do 1,5 mln', value: 1500000 },
  { label: 'do 2 mln', value: 2000000 },
];

const RENT_PRICE_PRESETS: { label: string; value: number }[] = [
  { label: 'do 2 tys.', value: 2000 },
  { label: 'do 3 tys.', value: 3000 },
  { label: 'do 4 tys.', value: 4000 },
  { label: 'do 6 tys.', value: 6000 },
  { label: 'do 8 tys.', value: 8000 },
];

const MORE_FILTERS: { key: string; label: string; options: string[] }[] = [
  { key: 'pietro', label: 'Piętro', options: ['Dowolne', 'Parter', '1–3 piętro', '4–10 piętro', 'Powyżej 10'] },
  { key: 'balkon', label: 'Balkon / Taras', options: ['Dowolne', 'Balkon', 'Taras', 'Loggia', 'Ogródek'] },
  { key: 'stan', label: 'Stan mieszkania', options: ['Dowolne', 'Do wykończenia', 'Do odświeżenia', 'Dobry', 'Po remoncie', 'Wysoki standard'] },
  { key: 'winda', label: 'Winda', options: ['Dowolne', 'Tak', 'Nie'] },
  { key: 'ogrzewanie', label: 'Ogrzewanie', options: ['Dowolne', 'Miejskie', 'Gazowe', 'Elektryczne', 'Pompa ciepła'] },
  { key: 'parking', label: 'Miejsce parkingowe', options: ['Dowolne', 'Garaż', 'Hala garażowa', 'Miejsce naziemne', 'Brak'] },
  { key: 'czynsz', label: 'Czynsz', options: ['Dowolny', 'do 400 zł', 'do 600 zł', 'do 800 zł', 'do 1000 zł'] },
  { key: 'rok', label: 'Rok budowy', options: ['Dowolny', 'Do 1970', '1971–2000', '2001–2015', 'Po 2015'] },
];

const PRICE_SLIDER_MAX = 2000000;
const PRICE_SLIDER_STEP = 25000;
const RENT_PRICE_SLIDER_MAX = 15000;
const RENT_PRICE_SLIDER_STEP = 100;

const AREA_PRESETS: { label: string; min: number; max: number }[] = [
  { label: 'do 35 m²', min: 0, max: 35 },
  { label: '35–50 m²', min: 35, max: 50 },
  { label: '50–70 m²', min: 50, max: 70 },
  { label: '70–90 m²', min: 70, max: 90 },
  { label: 'pow. 90 m²', min: 90, max: 0 },
];

const AREA_SLIDER_MAX = 150;
const AREA_SLIDER_STEP = 5;

const ROOM_OPTIONS = [1, 2, 3, 4, 5];

function roomsLabel(rooms: number): string {
  return `${rooms} ${ROOM_WORDS[rooms] ?? 'pokoi'}`;
}

function formatPln(value: number): string {
  return `${value.toLocaleString('pl-PL').replace(/\u00a0/g, ' ')} zł`;
}

/**
 * Konwertuje ulotny blob/URL zdjecia na skompresowany data URL (JPEG), przeskalowany do maks.
 * `maxDim` px po dluzszym boku - dzieki temu zdjecia da sie utrwalic w bazie bez rozdmuchiwania jej.
 */
async function imageUrlToDataUrl(url: string, maxDim = 1280, quality = 0.72): Promise<string | null> {
  try {
    const blob = await (await fetch(url)).blob();
    const bitmap = await createImageBitmap(blob);
    const scale = Math.min(1, maxDim / Math.max(bitmap.width, bitmap.height));
    const width = Math.max(1, Math.round(bitmap.width * scale));
    const height = Math.max(1, Math.round(bitmap.height * scale));
    const canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;
    const ctx = canvas.getContext('2d');
    if (!ctx) {
      return null;
    }
    ctx.drawImage(bitmap, 0, 0, width, height);
    bitmap.close();
    return canvas.toDataURL('image/jpeg', quality);
  } catch {
    return null;
  }
}

/**
 * Bezpieczny generator ID. crypto.randomUUID istnieje tylko w bezpiecznym kontekscie (HTTPS/localhost);
 * przy dostepie po IP w sieci lokalnej jest undefined i rzucalby wyjatek, wywalajac apke do bialego ekranu.
 */
function safeRandomId(): string {
  const webCrypto = typeof crypto !== 'undefined' ? crypto : undefined;
  if (webCrypto && typeof webCrypto.randomUUID === 'function') {
    return webCrypto.randomUUID();
  }
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
}

function offerMeta(offer: BuyOffer): string {
  const perMeter = Math.round(offer.priceValue / offer.area);
  const roomPart = offer.propertyType === 'Działka' ? offer.propertyType : roomsLabel(offer.rooms);
  return `${roomPart}  •  ${offer.area} m²  •  ${formatPln(perMeter).replace(' zł', '')} zł/m²  •  ${offer.year} r.`;
}

type NegotiationChance = {
  label: 'Wysoka' | 'Średnia' | 'Niska';
  text: string;
};

type NegotiationModel = {
  offerPerMeter: number;
  minPrice: number;
  maxPrice: number;
  minPerMeter: number;
  maxPerMeter: number;
  negotiationSpan: number;
  expectedSaving: number;
  chance: NegotiationChance;
  marketAvgPerMeter: number;
  marketDeltaPct: number;
  conditionDiscountPct: number;
  marketDays: number;
  seasonNegotiationBonusPct: number;
};

type NegotiationTransaction = {
  street: string;
  meta: string;
  price: string;
  sqm: string;
  date: string;
  image: 'hero' | 'city';
};

function roundPrice(value: number): number {
  return Math.round(value / 100) * 100;
}

function negotiationChanceFromScore(score: number): NegotiationChance {
  if (score >= 76) {
    return {
      label: 'Wysoka',
      text: 'Sprzedający prawdopodobnie zaakceptuje ofertę w tym zakresie cenowym.',
    };
  }
  if (score >= 52) {
    return {
      label: 'Średnia',
      text: 'Oferta jest realna, ale sprzedający może oczekiwać mocniejszego uzasadnienia ceny.',
    };
  }
  return {
    label: 'Niska',
    text: 'Ten zakres może być zbyt agresywny. Rozważ podniesienie oferty startowej.',
  };
}

function buildNegotiationModel(offer: BuyOffer): NegotiationModel {
  const offerPerMeter = offer.priceValue / offer.area;
  const cityBase = VALUATION_PRICE_PER_SQM_BY_CITY[offer.city] ?? offerPerMeter * 0.94;
  const premiumToCity = cityBase > 0 ? (offerPerMeter - cityBase) / cityBase : 0;
  const age = Math.max(0, 2026 - offer.year);
  const marketFactor = offer.market === 'wtorny' ? 0.017 : 0.007;
  const ageFactor = clampNumber(age / 145, 0, 0.07);
  const opportunityFactor = offer.tags.includes('Okazja') ? 0.013 : 0;
  const premiumFactor = Math.max(0, premiumToCity) * 0.5;
  const baselineDiscount = clampNumber(0.026 + marketFactor + ageFactor + opportunityFactor + premiumFactor, 0.02, 0.165);
  const spreadDiscount = clampNumber(0.014 + Math.max(0, premiumToCity) * 0.16 + age / 620, 0.012, 0.052);
  const rawMax = roundPrice(offer.priceValue * (1 - baselineDiscount));
  const rawMin = roundPrice(offer.priceValue * (1 - baselineDiscount - spreadDiscount));
  const floorMin = roundPrice(offer.priceValue * 0.75);
  const minPrice = Math.max(floorMin, Math.min(rawMin, rawMax - 5000));
  const maxPrice = Math.max(minPrice + 5000, Math.min(rawMax, offer.priceValue - 2000));
  const minPerMeter = Math.round(minPrice / offer.area);
  const maxPerMeter = Math.round(maxPrice / offer.area);
  const negotiationSpan = maxPrice - minPrice;
  const expectedSaving = offer.priceValue - Math.round((minPrice + maxPrice) / 2);
  const chanceScore = clampNumber(
    61
      + (offer.market === 'wtorny' ? 11 : -4)
      + (offer.tags.includes('Okazja') ? 7 : 0)
      + Math.max(0, premiumToCity) * 62
      + Math.min(0, premiumToCity) * 22
      + Math.min(age / 2.7, 14),
    24,
    96,
  );

  return {
    offerPerMeter: Math.round(offerPerMeter),
    minPrice,
    maxPrice,
    minPerMeter,
    maxPerMeter,
    negotiationSpan,
    expectedSaving,
    chance: negotiationChanceFromScore(chanceScore),
    marketAvgPerMeter: Math.round(clampNumber((minPerMeter * 0.55 + maxPerMeter * 0.45), 1000, 40000)),
    marketDeltaPct: Number((clampNumber((offerPerMeter - maxPerMeter) / offerPerMeter, -0.03, 0.18) * 100).toFixed(1)),
    conditionDiscountPct: Number((clampNumber((age / 2.2) * 0.35 + (offer.market === 'wtorny' ? 2.2 : 1.1), 1.2, 8.4)).toFixed(1)),
    marketDays: Math.round(clampNumber(38 + age * 1.05 + (offer.market === 'wtorny' ? 11 : 5), 28, 124)),
    seasonNegotiationBonusPct: Number((clampNumber(1.2 + (offer.market === 'wtorny' ? 1 : 0.5) + Math.max(0, premiumToCity) * 3.5, 0.8, 5.6)).toFixed(1)),
  };
}

function buildComparableTransactions(activeOffer: BuyOffer, source: BuyOffer[]): NegotiationTransaction[] {
  const sameCityAndType = source
    .filter((offer) => offer !== activeOffer && offer.city === activeOffer.city && offer.propertyType === activeOffer.propertyType)
    .sort((left, right) => Math.abs(left.area - activeOffer.area) - Math.abs(right.area - activeOffer.area));
  const sameCity = source
    .filter((offer) => offer !== activeOffer && offer.city === activeOffer.city)
    .sort((left, right) => Math.abs(left.area - activeOffer.area) - Math.abs(right.area - activeOffer.area));
  const fallback = source
    .filter((offer) => offer !== activeOffer)
    .sort((left, right) => Math.abs(left.area - activeOffer.area) - Math.abs(right.area - activeOffer.area));
  const merged = [...sameCityAndType, ...sameCity, ...fallback];
  const unique: BuyOffer[] = [];
  const keys = new Set<string>();
  for (const offer of merged) {
    const key = `${offer.city}|${offer.district}|${offer.title}|${offer.priceValue}`;
    if (keys.has(key)) {
      continue;
    }
    keys.add(key);
    unique.push(offer);
    if (unique.length >= 3) {
      break;
    }
  }
  return unique.map((offer) => {
    const perMeter = Math.round(offer.priceValue / offer.area);
    return {
      street: offer.district,
      meta: `${offer.area} m² · ${roomsLabel(offer.rooms)} · ${offer.year} r.`,
      price: formatPln(offer.priceValue),
      sqm: `${formatPln(perMeter).replace(' zł', '')} zł/m²`,
      date: offer.date,
      image: offer.image,
    };
  });
}

type RentOffer = {
  title: string;
  propertyType: PropertySearchType;
  city: string;
  district: string;
  rooms: number;
  area: number;
  priceValue: number;
  market: 'wtorny' | 'pierwotny';
  floor: string;
  buildingFloors: string;
  date: string;
  dateOrder: number;
  tags: string[];
  amenities: string[];
  position: string;
  forStudents: boolean;
  petsAllowed: boolean;
  parkingIncluded: boolean;
};

const rentListings: RentOffer[] = [
  {
    title: 'Mieszkanie 2-pokojowe',
    propertyType: 'Mieszkanie',
    city: 'Warszawa',
    district: 'Warszawa, Mokotów, Służew',
    rooms: 2,
    area: 42,
    priceValue: 3300,
    market: 'wtorny',
    floor: '3',
    buildingFloors: '6',
    date: 'Dodane dzisiaj',
    dateOrder: 0,
    tags: ['Nowe', 'Bez prowizji'],
    amenities: ['Umeblowane', 'Balkon', 'Winda'],
    position: '47% 58%',
    forStudents: true,
    petsAllowed: false,
    parkingIncluded: false,
  },
  {
    title: 'Mieszkanie 1-pokojowe',
    propertyType: 'Mieszkanie',
    city: 'Warszawa',
    district: 'Warszawa, Praga-Południe, Grochów',
    rooms: 1,
    area: 28,
    priceValue: 2200,
    market: 'wtorny',
    floor: '2',
    buildingFloors: '4',
    date: 'Dodane dzisiaj',
    dateOrder: 0,
    tags: ['Bez prowizji'],
    amenities: ['Umeblowane', 'Winda'],
    position: '86% 55%',
    forStudents: true,
    petsAllowed: false,
    parkingIncluded: false,
  },
  {
    title: 'Mieszkanie 3-pokojowe',
    propertyType: 'Mieszkanie',
    city: 'Warszawa',
    district: 'Warszawa, Bemowo, Górce',
    rooms: 3,
    area: 55,
    priceValue: 3800,
    market: 'pierwotny',
    floor: '1',
    buildingFloors: '5',
    date: 'Dodane wczoraj',
    dateOrder: 1,
    tags: ['Bez prowizji'],
    amenities: ['Umeblowane', 'Balkon', 'Parking'],
    position: '58% 62%',
    forStudents: false,
    petsAllowed: true,
    parkingIncluded: true,
  },
  {
    title: 'Mieszkanie 2-pokojowe',
    propertyType: 'Mieszkanie',
    city: 'Warszawa',
    district: 'Warszawa, Wola, Nowolipki',
    rooms: 2,
    area: 38,
    priceValue: 2900,
    market: 'wtorny',
    floor: '4',
    buildingFloors: '7',
    date: 'Dodane wczoraj',
    dateOrder: 1,
    tags: ['Bez prowizji'],
    amenities: ['Umeblowane', 'Winda'],
    position: '33% 58%',
    forStudents: false,
    petsAllowed: true,
    parkingIncluded: false,
  },
];

// slug trafia do adresu listy firm: /firmy?kategoria=remonty
const serviceCategories = [
  {
    slug: 'finanse',
    icon: 'credit-card',
    title: 'Finanse',
    text: 'Kredyty hipoteczne, doradcy, ubezpieczenia',
  },
  {
    slug: 'prawo',
    icon: 'scale',
    title: 'Prawo i dokumenty',
    text: 'Notariusze, prawnicy, rzeczoznawcy',
  },
  {
    slug: 'remonty',
    icon: 'paint',
    title: 'Remont i wykończenie',
    text: 'Firmy remontowe, fachowcy, projekty wnętrz',
  },
  {
    slug: 'przeprowadzka',
    icon: 'truck',
    title: 'Przeprowadzka',
    text: 'Firmy przeprowadzkowe, magazyny, transport',
  },
  {
    slug: 'wyposazenie',
    icon: 'sofa',
    title: 'Wyposażenie wnętrz',
    text: 'Meble, AGD, kuchnie na wymiar, oświetlenie',
  },
  {
    slug: 'sprzedaz',
    icon: 'camera',
    title: 'Sprzedaż nieruchomości',
    text: 'Fotografia, home staging, wirtualne spacery 3D',
  },
  {
    slug: 'deweloperzy',
    icon: 'city',
    title: 'Deweloperzy i inwestycje',
    text: 'Nowe inwestycje, deweloperzy, biura sprzedaży',
  },
  {
    slug: 'inne',
    icon: 'dots',
    title: 'Inne usługi',
    text: 'Usługi dodatkowe związane z nieruchomościami',
  },
];

type ServiceCategorySlug = (typeof serviceCategories)[number]['slug'];

const serviceCompanies = [
  {
    icon: 'house',
    title: 'Finanse Expert',
    rating: '4.9 (128 opinii)',
    text: 'Kredyty hipoteczne',
    city: 'Warszawa, cała Polska',
  },
  {
    icon: 'hammer',
    title: 'Remont Masters',
    rating: '4.8 (96 opinii)',
    text: 'Remonty i wykończenia',
    city: 'Kraków, małopolskie',
  },
  {
    icon: 'letter',
    title: 'Notariusz Nowak',
    rating: '4.9 (74 opinie)',
    text: 'Kancelarie notarialne',
    city: 'Wrocław, dolnośląskie',
  },
  {
    icon: 'truck',
    title: 'SuperPrzeprowadzki',
    rating: '4.7 (111 opinii)',
    text: 'Przeprowadzki',
    city: 'Poznań, wielkopolskie',
  },
];

const serviceDirectory = [
  {
    title: 'Remont Masters',
    rating: '4.8 (96 opinii)',
    text: 'Kompleksowe remonty mieszkań i domów',
    city: 'Kraków, małopolskie',
    image: heroImage,
    position: '72% 62%',
  },
  {
    title: 'Perfect House',
    rating: '4.7 (64 opinie)',
    text: 'Wykończenia wnętrz pod klucz',
    city: 'Warszawa, mazowieckie',
    image: heroImage,
    position: '92% 50%',
  },
  {
    title: 'Home Finish',
    rating: '4.6 (52 opinie)',
    text: 'Remonty, wykończenia, projekty wnętrz',
    city: 'Wrocław, dolnośląskie',
    image: heroImage,
    position: '48% 58%',
  },
];

// Pelna baza firm dla listy /firmy. Kazda pozycja ma kategorie, wiec filtrowanie
// po ?kategoria= dziala na realnych danych, a nie tylko podswietla zakladke.
const companyDirectory: Array<{
  title: string;
  category: ServiceCategorySlug;
  rating: string;
  text: string;
  city: string;
  image: string;
  position: string;
}> = [
  { title: 'Finanse Expert', category: 'finanse', rating: '4.9 (128 opinii)', text: 'Kredyty hipoteczne i doradztwo finansowe', city: 'Warszawa, cała Polska', image: cityImage, position: '50% 40%' },
  { title: 'Kredyt Partner', category: 'finanse', rating: '4.6 (57 opinii)', text: 'Porównanie ofert kredytowych, ubezpieczenia', city: 'Gdańsk, pomorskie', image: heroImage, position: '30% 55%' },
  { title: 'Notariusz Nowak', category: 'prawo', rating: '4.9 (74 opinie)', text: 'Kancelaria notarialna, akty i umowy', city: 'Wrocław, dolnośląskie', image: heroImage, position: '62% 45%' },
  { title: 'Lex Nieruchomości', category: 'prawo', rating: '4.7 (41 opinii)', text: 'Obsługa prawna transakcji, księgi wieczyste', city: 'Warszawa, mazowieckie', image: cityImage, position: '70% 60%' },
  { title: 'Remont Masters', category: 'remonty', rating: '4.8 (96 opinii)', text: 'Kompleksowe remonty mieszkań i domów', city: 'Kraków, małopolskie', image: heroImage, position: '72% 62%' },
  { title: 'Perfect House', category: 'remonty', rating: '4.7 (64 opinie)', text: 'Wykończenia wnętrz pod klucz', city: 'Warszawa, mazowieckie', image: heroImage, position: '92% 50%' },
  { title: 'Home Finish', category: 'remonty', rating: '4.6 (52 opinie)', text: 'Remonty, wykończenia, projekty wnętrz', city: 'Wrocław, dolnośląskie', image: heroImage, position: '48% 58%' },
  { title: 'SuperPrzeprowadzki', category: 'przeprowadzka', rating: '4.7 (111 opinii)', text: 'Przeprowadzki lokalne i krajowe', city: 'Poznań, wielkopolskie', image: cityImage, position: '40% 50%' },
  { title: 'Transport 24', category: 'przeprowadzka', rating: '4.5 (38 opinii)', text: 'Transport mebli, magazynowanie, pakowanie', city: 'Łódź, łódzkie', image: heroImage, position: '20% 70%' },
  { title: 'Kuchnie na Wymiar', category: 'wyposazenie', rating: '4.8 (83 opinie)', text: 'Meble kuchenne i zabudowy na wymiar', city: 'Katowice, śląskie', image: heroImage, position: '85% 40%' },
  { title: 'Salon Oświetlenia Lumo', category: 'wyposazenie', rating: '4.4 (29 opinii)', text: 'Oświetlenie, AGD, wyposażenie wnętrz', city: 'Szczecin, zachodniopomorskie', image: cityImage, position: '60% 35%' },
  { title: 'FotoNieruchomości', category: 'sprzedaz', rating: '4.9 (67 opinii)', text: 'Fotografia ofert, home staging, spacery 3D', city: 'Warszawa, cała Polska', image: cityImage, position: '35% 65%' },
  { title: 'Studio Wirtualnych Spacerów', category: 'sprzedaz', rating: '4.7 (45 opinii)', text: 'Wirtualne spacery 3D i plany mieszkań', city: 'Kraków, małopolskie', image: heroImage, position: '55% 48%' },
  { title: 'Nowa Inwestycja Development', category: 'deweloperzy', rating: '4.6 (58 opinii)', text: 'Nowe inwestycje mieszkaniowe, biuro sprzedaży', city: 'Gdynia, pomorskie', image: cityImage, position: '80% 55%' },
  { title: 'Osiedle Park Deweloper', category: 'deweloperzy', rating: '4.5 (34 opinie)', text: 'Mieszkania z rynku pierwotnego', city: 'Wrocław, dolnośląskie', image: heroImage, position: '45% 42%' },
  { title: 'Serwis Domowy Fix', category: 'inne', rating: '4.6 (72 opinie)', text: 'Drobne naprawy, złota rączka, przeglądy', city: 'Cała Polska', image: heroImage, position: '68% 68%' },
  { title: 'Czysto i Gotowe', category: 'inne', rating: '4.8 (91 opinii)', text: 'Sprzątanie po remoncie i przed wprowadzeniem', city: 'Warszawa, mazowieckie', image: cityImage, position: '25% 45%' },
];

const serviceSteps = [
  {
    icon: 'group',
    title: 'Wybierz usługę',
    text: 'Wybierz kategorię, która Cię interesuje',
  },
  {
    icon: 'search',
    title: 'Porównaj firmy',
    text: 'Sprawdź opinie, ceny i portfolio firm',
  },
  {
    icon: 'message',
    title: 'Skontaktuj się',
    text: 'Napisz lub zadzwoń bezpośrednio do firmy',
  },
  {
    icon: 'paint',
    title: 'Zrealizuj usługę',
    text: 'Ciesz się profesjonalnie wykonaną usługą',
  },
];

const sellSteps = [
  {
    icon: 'document',
    title: 'Dodaj ogłoszenie',
    text: 'Wypełnij formularz i dodaj zdjęcia',
  },
  {
    icon: 'calculator',
    title: 'My wycenimy się resztą',
    text: 'Twoje ogłoszenie trafi do tysięcy kupujących',
  },
  {
    icon: 'message',
    title: 'Otrzymujesz zapytania',
    text: 'Kupujący kontaktują się bezpośrednio z Tobą',
  },
  {
    icon: 'scale',
    title: 'Negocjujesz warunki',
    text: 'Rozmawiasz i ustalasz szczegóły sprzedaży',
  },
  {
    icon: 'money',
    title: 'Sprzedajesz',
    text: 'Finalizujesz transakcję i odbierasz pieniądze',
  },
];

const sellReasons = [
  {
    icon: 'check',
    title: 'Całkowicie za darmo',
    text: '0 zł prowizji, 0 zł opłat',
  },
  {
    icon: 'target',
    title: 'Kupujący z całej Polski',
    text: 'Docieramy do tysięcy osób każdego dnia',
  },
  {
    icon: 'spark',
    title: 'Sprzedaż nawet w 7 dni',
    text: 'Szybka sprzedaż dzięki dużej bazie kupujących',
  },
  {
    icon: 'shield',
    title: 'Pełna kontrola',
    text: 'To Ty decydujesz z kim rozmawiasz i na jakich warunkach',
  },
  {
    icon: 'warning',
    title: 'Wsparcie na każdym etapie',
    text: 'Pomocny asystent AI prowadzi przez cały proces',
  },
];

const sellChannels = [
  {
    icon: 'logo',
    title: 'mieszko.pl',
    text: 'Nasza strona',
  },
  {
    icon: 'search',
    title: 'Google',
    text: 'Reklama Google',
  },
  {
    icon: 'message',
    title: 'Facebook',
    text: 'Marketplace i posty',
  },
  {
    icon: 'group',
    title: 'Grupy lokalne',
    text: 'Ponad 500 grup',
  },
  {
    icon: 'mail',
    title: 'Newsletter',
    text: 'Tysiące subskrybentów',
  },
];

const sellFeatures = [
  {
    icon: 'star',
    title: 'Wyróżnione ogłoszenie',
    text: 'Twoje ogłoszenie na szczycie listy',
  },
  {
    icon: 'camera',
    title: 'Galeria zdjęć i video',
    text: 'Dodaj do 30 zdjęć i film',
  },
  {
    icon: 'tour',
    title: 'Wirtualny spacer 360°',
    text: 'Pokaż mieszkanie w 3D',
  },
  {
    icon: 'chart',
    title: 'Statystyki oglądalności',
    text: 'Sprawdzaj zainteresowanie',
  },
  {
    icon: 'bell',
    title: 'Powiadomienia o nowych',
    text: 'Bądź pierwszy w kontaktach',
  },
];

const sellTestimonials = [
  {
    name: 'Anna, Kraków',
    result: 'Sprzedane w 6 dni',
    text: 'Dodałam ogłoszenie w poniedziałek, w sobotę miałam już kupca! Zero prowizji, świetny kontakt.',
  },
  {
    name: 'Marek, Warszawa',
    result: 'Sprzedane w 4 dni',
    text: 'Najlepszy portal do sprzedaży mieszkań. Dużo zapytań i poważnych kupujących.',
  },
  {
    name: 'Kasia, Wrocław',
    result: 'Sprzedane w 7 dni',
    text: 'Prosty formularz, szybka publikacja i bardzo wszystkie zero kosztów. Polecam!',
  },
];

const addProgressSteps = ['Szczegóły', 'Lokalizacja', 'Zdjęcia', 'Dodatkowe informacje', 'Podgląd ogłoszenia'];

const listingTips = [
  {
    icon: 'document',
    title: 'Napisz szczegółowy opis',
    text: 'Im więcej informacji, tym większe zainteresowanie.',
  },
  {
    icon: 'money',
    title: 'Ustal dokładną cenę',
    text: 'Sprawdź ceny podobnych mieszkań w okolicy.',
  },
  {
    icon: 'lock',
    title: 'Bądź w kontakcie',
    text: 'Szybkie odpowiedzi zwiększają szanse na sprzedaż.',
  },
];

const extraInfoTips = [
  {
    icon: 'document',
    title: 'Im więcej informacji, tym lepiej',
    text: 'Szczegółowy opis zwiększa zaufanie i zainteresowanie ofertą.',
  },
  {
    icon: 'shield',
    title: 'Sprawdź dokładnie dane',
    text: 'Upewnij się, że wszystkie informacje są poprawne.',
  },
  {
    icon: 'star',
    title: 'Wyróżnij atuty nieruchomości',
    text: 'Dodatkowe udogodnienia mogą przyciągnąć kupujących.',
  },
];

const mediaOptions = [
  { icon: 'flame', label: 'Ogrzewanie miejskie', checked: true },
  { icon: 'flame', label: 'Ogrzewanie gazowe' },
  { icon: 'house', label: 'Ogrzewanie elektryczne' },
  { icon: 'building', label: 'Ogrzewanie podłogowe' },
  { icon: 'sun', label: 'Woda miejska', checked: true },
  { icon: 'link', label: 'Kanalizacja', checked: true },
  { icon: 'link', label: 'Gospodarka ściekowa' },
  { icon: 'spark', label: 'Prąd', checked: true },
  { icon: 'flame', label: 'Gaz' },
  { icon: 'wifi', label: 'Internet', checked: true },
  { icon: 'wifi', label: 'Światłowód' },
  { icon: 'screen', label: 'Telewizja kablowa' },
  { icon: 'shield', label: 'Alarm' },
  { icon: 'phone', label: 'Domofon' },
  { icon: 'screen', label: 'Wideodomofon' },
  { icon: 'spark', label: 'Smart Home' },
  { icon: 'sun', label: 'Fotowoltaika' },
  { icon: 'snow', label: 'Pompa ciepła' },
  { icon: 'building', label: 'Rekuperacja' },
  { icon: 'snow', label: 'Klimatyzacja' },
];

const RENT_BILLING_OPTIONS_HEATING = ['Wliczone w cenę', 'Według zużycia', 'Stała opłata'];
const RENT_BILLING_OPTIONS_WATER = ['Wliczona w cenę', 'Według zużycia', 'Stała opłata'];
const RENT_BILLING_OPTIONS_ENERGY = ['Wliczony w cenę', 'Według zużycia', 'Stała opłata'];
const RENT_BILLING_OPTIONS_INTERNET = ['Wliczony w cenę', 'Dodatkowo płatny (kwota)'];
const RENT_BILLING_OPTIONS_TV = ['Wliczona w cenę', 'Dodatkowo płatna (kwota)'];
const RENT_BILLING_OPTIONS_PARKING = ['W cenie', 'Dodatkowo płatny (kwota/mies.)'];

function billingRequiresAmount(mode: string): boolean {
  return mode === 'Stała opłata' || mode.includes('(kwota');
}

function getRentBillingOptions(label: string): string[] | null {
  if (label.startsWith('Ogrzewanie')) {
    return RENT_BILLING_OPTIONS_HEATING;
  }
  if (label === 'Woda miejska') {
    return RENT_BILLING_OPTIONS_WATER;
  }
  if (label === 'Prąd' || label === 'Gaz') {
    return RENT_BILLING_OPTIONS_ENERGY;
  }
  if (label === 'Internet') {
    return RENT_BILLING_OPTIONS_INTERNET;
  }
  if (label === 'Telewizja kablowa') {
    return RENT_BILLING_OPTIONS_TV;
  }
  return null;
}

const amenityOptions = [
  { icon: 'building', label: 'Balkon', checked: true },
  { icon: 'building', label: 'Taras' },
  { icon: 'wifi', label: 'Ogródek' },
  { icon: 'city', label: 'Winda', checked: true },
  { icon: 'car', label: 'Parking', checked: true },
  { icon: 'car', label: 'Garaż' },
  { icon: 'building', label: 'Strych' },
  { icon: 'shield', label: 'Ogrodzenie' },
  { icon: 'student', label: 'Dla studentów' },
  { icon: 'paw', label: 'Zwierzęta mile widziane' },
  { icon: 'building', label: 'Piwnica', checked: true },
  { icon: 'building', label: 'Komórka lokatorska' },
  { icon: 'document', label: 'Dostęp dla niepełnosprawnych' },
  { icon: 'eye', label: 'Monitoring' },
  { icon: 'shield', label: 'Ochrona' },
];

const houseAmenityOptions = [
  { icon: 'flame', label: 'Kominek' },
  { icon: 'sun', label: 'Basen' },
  { icon: 'sun', label: 'Jacuzzi' },
  { icon: 'snow', label: 'Sauna' },
  { icon: 'sun', label: 'Ogród zimowy' },
  { icon: 'house', label: 'Altana' },
  { icon: 'car', label: 'Automatyczna brama' },
  { icon: 'shield', label: 'Rolety zewnętrzne' },
  { icon: 'eye', label: 'Monitoring zewnętrzny' },
  { icon: 'shield', label: 'System alarmowy' },
  { icon: 'sun', label: 'Panele fotowoltaiczne' },
  { icon: 'snow', label: 'Pompa ciepła' },
  { icon: 'spark', label: 'Magazynek energii' },
  { icon: 'car', label: 'Ładowarka do samochodu elektrycznego' },
  { icon: 'flame', label: 'Grill murowany' },
  { icon: 'sun', label: 'Oczko wodne' },
  { icon: 'student', label: 'Plac zabaw' },
  { icon: 'target', label: 'Boisko' },
];

const extraInfoSelectOptions = {
  rentalPeriod: ['Wybierz', 'Krótkoterminowy', 'Długoterminowy', 'Bez określonego czasu'],
  furnishing: ['Wybierz', 'Tak', 'Częściowo', 'Nie'],
  buildingType: ['Wybierz', 'Blok', 'Kamienica', 'Apartamentowiec', 'Dom wielorodzinny', 'Loft'],
  houseType: ['Wybierz', 'Wolnostojący', 'Bliźniak', 'Szeregowy', 'Segment skrajny', 'Segment środkowy', 'Dworek', 'Rezydencja'],
  buildingMaterial: ['Wybierz', 'Cegła', 'Pustak', 'Beton', 'Drewno', 'Keramzyt', 'Silikat', 'Prefabrykat', 'Inny'],
  windows: ['Wybierz', 'PCV', 'Drewniane', 'Aluminiowe'],
  exposure: ['Wybierz', 'Północ', 'Południe', 'Wschód', 'Zachód', 'Północ-południe', 'Wschód-zachód', 'Wielostronna'],
  houseFloors: ['Wybierz', 'Parterowy', '1', '2', '3+'],
  roofType: ['Wybierz', 'Dwuspadowy', 'Czterospadowy', 'Płaski', 'Wielospadowy'],
  roofCover: ['Wybierz', 'Dachówka', 'Blachodachówka', 'Blacha', 'Papa', 'Gont', 'Inne'],
  garageDetails: ['Wybierz', '1 stanowisko', '2 stanowiska', '3+', 'W bryle budynku', 'Wolnostojący'],
  basement: ['Wybierz', 'Tak', 'Częściowe', 'Nie'],
  attic: ['Wybierz', 'Użytkowy', 'Nieużytkowy', 'Brak'],
  fencing: ['Wybierz', 'Tak', 'Częściowe', 'Nie'],
  accessRoad: ['Wybierz', 'Asfalt', 'Kostka', 'Utwardzony', 'Szutrowy', 'Polny'],
  sewageType: ['Wybierz', 'Kanalizacja', 'Szambo', 'Przydomowa oczyszczalnia'],
};

const listingDetailsSelectOptions = {
  transaction: ['Sprzedaż', 'Wynajem'],
  propertyType: ['Mieszkanie', 'Dom', 'Działka', 'Lokal użytkowy', 'Pokój'],
  market: ['Wtórny', 'Pierwotny'],
  houseType: ['Wybierz', 'Wolnostojący', 'Bliźniak', 'Szeregowy', 'Segment skrajny', 'Segment środkowy', 'Rezydencja', 'Dworek'],
  houseFloors: ['Wybierz', 'Parterowy', '1', '2', '3+'],
  houseGarage: ['Wybierz', 'Brak', 'Jednostanowiskowy', 'Dwustanowiskowy', 'Trzystanowiskowy+', 'W bryle budynku', 'Wolnostojący'],
  houseBasement: ['Wybierz', 'Tak', 'Nie', 'Częściowe'],
  houseAttic: ['Wybierz', 'Użytkowy', 'Nieużytkowy', 'Brak'],
  rooms: ['Wybierz', '1', '2', '3', '4', '5', '6+'],
  floor: ['Wybierz', 'Parter', '1', '2', '3', '4', '5', '6', '7', '8+', 'Poddasze'],
  buildingFloors: ['Wybierz', '1', '2', '3', '4', '5', '6', '7', '8+'],
  condition: ['Wybierz', 'Do wprowadzenia', 'Po remoncie', 'Do odświeżenia', 'Do remontu', 'Deweloperski'],
  ownership: ['Wybierz', 'Pełna własność', 'Spółdzielcze własnościowe', 'Użytkowanie wieczyste', 'Udział'],
};

const INITIAL_LISTING_DETAILS = {
  transaction: 'Sprzedaż',
  propertyType: 'Mieszkanie',
  market: 'Wtórny',
  title: '',
  description: '',
  price: '',
  area: '',
  rooms: 'Wybierz',
  houseType: 'Wybierz',
  houseGarage: 'Wybierz',
  houseBasement: 'Wybierz',
  houseAttic: 'Wybierz',
  floor: 'Wybierz',
  buildingFloors: 'Wybierz',
  houseFloors: 'Wybierz',
  yearBuilt: '',
  condition: 'Wybierz',
  ownership: 'Wybierz',
  contactName: '',
  contactPhone: '',
  contactEmail: '',
};

const INITIAL_EXTRA_INFO = {
  rent: '',
  deposit: '',
  rentalPeriod: 'Wybierz',
  mediaBilling: {} as Record<string, string>,
  mediaBillingAmounts: {} as Record<string, string>,
  availableFrom: '',
  plotArea: '',
  furnishing: 'Wybierz',
  buildingType: 'Wybierz',
  buildingMaterial: 'Wybierz',
  roofType: 'Wybierz',
  roofCover: 'Wybierz',
  garageDetails: 'Wybierz',
  basement: 'Wybierz',
  attic: 'Wybierz',
  fencing: 'Wybierz',
  accessRoad: 'Wybierz',
  sewageType: 'Wybierz',
  windows: 'Wybierz',
  exposure: 'Wybierz',
  roomHeight: '',
  virtualTourUrl: '',
};

type VirtualTourEmbedInfo = {
  embedUrl: string;
  providerLabel: string;
};

function normalizeVirtualTourInput(rawValue: string): string {
  const trimmed = rawValue.trim();
  if (!trimmed) {
    throw new Error('Wklej pełny link do wirtualnego spaceru.');
  }

  let parsed: URL;
  try {
    parsed = new URL(trimmed);
  } catch {
    throw new Error('Podaj poprawny adres URL spaceru (http:// lub https://).');
  }

  if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
    throw new Error('Link do spaceru musi zaczynać się od http:// lub https://.');
  }

  return parsed.toString();
}

function getVirtualTourEmbed(url: string): VirtualTourEmbedInfo | null {
  let parsed: URL;
  try {
    parsed = new URL(url);
  } catch {
    return null;
  }

  const host = parsed.hostname.toLowerCase();
  const pathParts = parsed.pathname.split('/').filter(Boolean);

  if (host === 'youtu.be' || host.endsWith('.youtu.be')) {
    const id = pathParts[0];
    if (id) {
      return {
        embedUrl: `https://www.youtube.com/embed/${encodeURIComponent(id)}`,
        providerLabel: 'YouTube',
      };
    }
  }

  if (host === 'youtube.com' || host.endsWith('.youtube.com')) {
    const videoId = parsed.searchParams.get('v')
      || (pathParts[0] === 'embed' ? pathParts[1] : null)
      || (pathParts[0] === 'shorts' ? pathParts[1] : null);
    if (videoId) {
      return {
        embedUrl: `https://www.youtube.com/embed/${encodeURIComponent(videoId)}`,
        providerLabel: 'YouTube',
      };
    }
  }

  if (host.endsWith('matterport.com')) {
    const modelId = parsed.searchParams.get('m');
    if (modelId) {
      return {
        embedUrl: `https://my.matterport.com/show/?m=${encodeURIComponent(modelId)}`,
        providerLabel: 'Matterport',
      };
    }
    if (pathParts[0] === 'show') {
      return {
        embedUrl: parsed.toString(),
        providerLabel: 'Matterport',
      };
    }
  }

  if (host.endsWith('kuula.co')) {
    if (pathParts[0] === 'post' && pathParts[1]) {
      return {
        embedUrl: `https://kuula.co/share/${encodeURIComponent(pathParts[1])}?fs=1&vr=1&thumbs=1&info=1&logo=1`,
        providerLabel: 'Kuula',
      };
    }
    if (pathParts[0] === 'share' && pathParts[1]) {
      return {
        embedUrl: parsed.toString(),
        providerLabel: 'Kuula',
      };
    }
  }

  return null;
}

const EDIT_LISTING_DRAFT_STORAGE_KEY = 'polskalokalnie-edit-listing-draft';

const addFooterBenefits = [
  {
    icon: 'lock',
    title: 'Szyfrowane dane',
    text: 'Zabezpieczamy 100% przesyłanych informacji.',
  },
  {
    icon: 'group',
    title: 'Tysiące kupujących',
    text: 'Twoje ogłoszenie zobaczą zainteresowani.',
  },
  {
    icon: 'clock',
    title: 'Prosta obsługa',
    text: 'Dodaj ogłoszenie w mniej niż 5 minut.',
  },
  {
    icon: 'shield',
    title: 'Zero kosztów',
    text: 'Jesteśmy za darmo. Od początku do końca.',
  },
];

const guideTopics = [
  {
    icon: 'house',
    title: 'Kupno mieszkania',
    text: 'Poradniki dla kupujących',
    count: '23 artykuły',
  },
  {
    icon: 'percent',
    title: 'Kredyty hipoteczne',
    text: 'Finansowanie zakupu',
    count: '18 artykułów',
  },
  {
    icon: 'key',
    title: 'Wynajem',
    text: 'Porady dla najemców i wynajmujących',
    count: '16 artykułów',
  },
  {
    icon: 'chart',
    title: 'Inwestowanie',
    text: 'Analizy i strategie inwestycyjne',
    count: '15 artykułów',
  },
  {
    icon: 'paint',
    title: 'Remont i wykończenie',
    text: 'Praktyczne porady i inspiracje',
    count: '21 artykułów',
  },
  {
    icon: 'scale',
    title: 'Prawo i podatki',
    text: 'Prawo nieruchomości i podatki',
    count: '17 artykułów',
  },
];

const guideArticles = [
  {
    category: 'KREDYTY HIPOTECZNE',
    title: 'Jak obliczyć zdolność kredytową w 2026 roku?',
    text: 'Praktyczny przewodnik krok po kroku z aktualnymi przykładami.',
    readTime: '8 min czytania',
    views: '12 450',
    image: cityImage,
    position: '24% 58%',
  },
  {
    category: 'KUPNO MIESZKANIA',
    title: 'Na co zwrócić uwagę przy odbiorze mieszkania?',
    text: 'Sprawdź listę rzeczy, które warto skontrolować przed podpisaniem protokołu.',
    readTime: '10 min czytania',
    views: '9 876',
    image: heroImage,
    position: '47% 58%',
  },
  {
    category: 'WYNAJEM',
    title: 'Umowa najmu okazjonalnego - wzór i omówienie',
    text: 'Kompletny wzór umowy z objaśnieniami i praktycznymi wskazówkami.',
    readTime: '6 min czytania',
    views: '7 532',
    image: heroImage,
    position: '74% 55%',
  },
  {
    category: 'INWESTOWANIE',
    title: 'Gdzie warto kupić mieszkanie inwestycyjne w 2026?',
    text: 'Analiza miast i dzielnic z największym potencjałem wzrostu.',
    readTime: '12 min czytania',
    views: '11 302',
    image: cityImage,
    position: '82% 56%',
  },
];

const quickGuides = [
  {
    icon: 'scale',
    title: 'Jak sprawdzić księgę wieczystą?',
    text: 'Poradnik krok po kroku',
  },
  {
    icon: 'percent',
    title: 'Podatek od sprzedaży mieszkania',
    text: 'Kiedy trzeba zapłacić?',
  },
  {
    icon: 'paint',
    title: 'Ile kosztuje remont mieszkania?',
    text: 'Aktualne koszty robocizny i materiałów',
  },
  {
    icon: 'shield',
    title: 'Jak bezpiecznie wynająć mieszkanie?',
    text: 'Porady dla wynajmujących',
  },
  {
    icon: 'chart',
    title: 'PCC przy zakupie mieszkania',
    text: 'Stawki i zwolnienia w 2026 roku',
  },
];

const guideRelatedOffers = [
  {
    title: 'Mieszkania w Krakowie',
    count: '2 450 ofert',
    image: cityImage,
    position: '88% 56%',
  },
  {
    title: 'Mieszkania w Warszawie',
    count: '5 120 ofert',
    image: cityImage,
    position: '18% 48%',
  },
  {
    title: 'Nowe mieszkania',
    count: '3 280 ofert',
    image: cityImage,
    position: '74% 52%',
  },
  {
    title: 'Okazje cenowe',
    count: '1 230 ofert',
    image: heroImage,
    position: '47% 58%',
  },
];

const mapMarkers = [
  ['649 tys.', '72%', '20%'],
  ['590 tys.', '50%', '33%'],
  ['669 tys.', '82%', '32%'],
  ['799 tys.', '18%', '45%'],
  ['699 tys.', '18%', '61%'],
  ['699 tys.', '60%', '59%'],
  ['790 tys.', '84%', '60%'],
  ['390 tys.', '61%', '73%'],
  ['760 tys.', '42%', '86%'],
  ['900 tys.', '72%', '86%'],
  ['360 tys.', '82%', '77%'],
];

const rentMapMarkers = [
  ['2 600 zł', '30%', '19%'],
  ['3 300 zł', '68%', '19%'],
  ['2 800 zł', '88%', '25%'],
  ['2 200 zł', '49%', '33%'],
  ['3 800 zł', '18%', '43%'],
  ['2 400 zł', '15%', '62%'],
  ['3 900 zł', '62%', '65%'],
  ['2 900 zł', '78%', '68%'],
  ['2 900 zł', '28%', '78%'],
  ['2 300 zł', '75%', '88%'],
];

type LatLng = { lat: number; lng: number };

const POLAND_CENTER: LatLng = { lat: 52.1, lng: 19.3 };

const CITY_COORDS: Record<string, LatLng> = {
  Warszawa: { lat: 52.2297, lng: 21.0122 },
  Kraków: { lat: 50.0647, lng: 19.945 },
  Łódź: { lat: 51.7592, lng: 19.456 },
  Wrocław: { lat: 51.1079, lng: 17.0385 },
  Poznań: { lat: 52.4064, lng: 16.9252 },
  Gdańsk: { lat: 54.352, lng: 18.6466 },
  Szczecin: { lat: 53.4285, lng: 14.5528 },
  Bydgoszcz: { lat: 53.1235, lng: 18.0084 },
  Lublin: { lat: 51.2465, lng: 22.5684 },
  Białystok: { lat: 53.1325, lng: 23.1688 },
  Katowice: { lat: 50.2649, lng: 19.0238 },
  Gdynia: { lat: 54.5189, lng: 18.5305 },
  Częstochowa: { lat: 50.7971, lng: 19.12 },
  Radom: { lat: 51.4027, lng: 21.1471 },
  Sosnowiec: { lat: 50.2862, lng: 19.104 },
  Toruń: { lat: 53.0138, lng: 18.5984 },
  Kielce: { lat: 50.8661, lng: 20.6286 },
  Rzeszów: { lat: 50.0413, lng: 21.999 },
  Gliwice: { lat: 50.2945, lng: 18.6714 },
  Zabrze: { lat: 50.3249, lng: 18.7857 },
  Olsztyn: { lat: 53.7784, lng: 20.4801 },
  'Bielsko-Biała': { lat: 49.8224, lng: 19.0444 },
  Bytom: { lat: 50.3483, lng: 18.9157 },
  'Zielona Góra': { lat: 51.9356, lng: 15.5062 },
  Rybnik: { lat: 50.0971, lng: 18.5416 },
  Opole: { lat: 50.6751, lng: 17.9213 },
  Tychy: { lat: 50.131, lng: 18.9866 },
  'Gorzów Wielkopolski': { lat: 52.7368, lng: 15.2288 },
  Płock: { lat: 52.5468, lng: 19.7064 },
  Tarnów: { lat: 50.0121, lng: 20.9858 },
  Koszalin: { lat: 54.1943, lng: 16.1714 },
  Kalisz: { lat: 51.7611, lng: 18.0911 },
  Legnica: { lat: 51.207, lng: 16.1619 },
  Słupsk: { lat: 54.4641, lng: 17.0287 },
  Elbląg: { lat: 54.1561, lng: 19.4045 },
  Wałbrzych: { lat: 50.7714, lng: 16.2845 },
  'Nowy Sącz': { lat: 49.6175, lng: 20.7153 },
  'Jelenia Góra': { lat: 50.9044, lng: 15.7197 },
  Sopot: { lat: 54.4418, lng: 18.5601 },
  Zakopane: { lat: 49.2992, lng: 19.9496 },
  Przemyśl: { lat: 49.7838, lng: 22.7677 },
};

const POLAND_BOUNDS = { minLat: 48.8, maxLat: 54.9, minLng: 14.0, maxLng: 24.2 };

function projectToWorld(lat: number, lng: number, zoom: number) {
  const scale = 256 * 2 ** zoom;
  const x = ((lng + 180) / 360) * scale;
  const sinLat = Math.sin((lat * Math.PI) / 180);
  const y = (0.5 - Math.log((1 + sinLat) / (1 - sinLat)) / (4 * Math.PI)) * scale;
  return { x, y };
}

function unprojectFromWorld(x: number, y: number, zoom: number): LatLng {
  const scale = 256 * 2 ** zoom;
  const lng = (x / scale) * 360 - 180;
  const n = Math.PI - (2 * Math.PI * y) / scale;
  const lat = (180 / Math.PI) * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)));
  return { lat, lng };
}

function clampCenter(center: LatLng): LatLng {
  return {
    lat: Math.min(POLAND_BOUNDS.maxLat, Math.max(POLAND_BOUNDS.minLat, center.lat)),
    lng: Math.min(POLAND_BOUNDS.maxLng, Math.max(POLAND_BOUNDS.minLng, center.lng)),
  };
}

function normalizeSearchText(value: string): string {
  return value.trim().toLowerCase().normalize('NFD').replace(/\p{Diacritic}/gu, '');
}

function cityStartsWith(cityName: string, query: string): boolean {
  const normalizedQuery = normalizeSearchText(query);
  if (!normalizedQuery) {
    return true;
  }
  return normalizeSearchText(cityName).startsWith(normalizedQuery);
}

function findKnownCityKey(name: string): string | null {
  const normalized = normalizeSearchText(name);
  if (!normalized) {
    return null;
  }
  return Object.keys(CITY_COORDS).find((cityName) => normalizeSearchText(cityName) === normalized) ?? null;
}

function useResolvedCityCenter(cityName: string, fallbackCenter: LatLng): LatLng {
  const [center, setCenter] = useState<LatLng>(fallbackCenter);

  useEffect(() => {
    const trimmed = cityName.trim();
    if (!trimmed) {
      setCenter(fallbackCenter);
      return;
    }

    const knownCity = findKnownCityKey(trimmed);
    if (knownCity) {
      setCenter(CITY_COORDS[knownCity]);
      return;
    }

    let cancelled = false;
    const controller = new AbortController();
    fetchPlaceSuggestions(trimmed, controller.signal)
      .then((places) => {
        if (cancelled || places.length === 0) {
          return;
        }
        setCenter(clampCenter({ lat: places[0].lat, lng: places[0].lng }));
      })
      .catch(() => undefined);

    return () => {
      cancelled = true;
      controller.abort();
    };
  }, [cityName, fallbackCenter.lat, fallbackCenter.lng]);

  return center;
}

function listingLatLng(offer: { city: string }, index: number): LatLng {
  const base = CITY_COORDS[offer.city] ?? POLAND_CENTER;
  const radius = 0.035;
  return {
    lat: base.lat + Math.sin(index * 1.7) * radius,
    lng: base.lng + Math.cos(index * 2.3) * radius * 1.4,
  };
}

type PlaceSuggestion = {
  id: number;
  label: string;
  sublabel: string;
  lat: number;
  lng: number;
};

const PLACE_TYPE_LABELS: Record<string, string> = {
  city: 'miasto',
  town: 'miasto',
  village: 'wieś',
  hamlet: 'przysiółek',
  administrative: 'gmina',
  municipality: 'gmina',
  suburb: 'dzielnica',
  neighbourhood: 'osiedle',
  quarter: 'dzielnica',
  county: 'powiat',
  state: 'województwo',
  locality: 'miejscowość',
};

type NominatimResult = {
  place_id: number;
  lat: string;
  lon: string;
  display_name: string;
  type: string;
  class: string;
  address?: Record<string, string>;
};

function describePlace(item: NominatimResult): PlaceSuggestion {
  const address = item.address ?? {};
  const label =
    address.city ?? address.town ?? address.village ?? address.hamlet
    ?? address.municipality ?? item.display_name.split(',')[0];
  const typeLabel = PLACE_TYPE_LABELS[item.type] ?? PLACE_TYPE_LABELS[item.class] ?? 'miejscowość';
  const county = address.county ? address.county.replace(/^powiat\s+/i, '') : '';
  const state = address.state ? address.state.replace(/^województwo\s+/i, '') : '';
  const sublabel = [typeLabel, county, state].filter(Boolean).join(', ');
  return {
    id: item.place_id,
    label,
    sublabel,
    lat: Number(item.lat),
    lng: Number(item.lon),
  };
}

async function fetchPlaceSuggestions(query: string, signal: AbortSignal): Promise<PlaceSuggestion[]> {
  const url = new URL('https://nominatim.openstreetmap.org/search');
  url.searchParams.set('q', query);
  url.searchParams.set('format', 'jsonv2');
  url.searchParams.set('countrycodes', 'pl');
  url.searchParams.set('addressdetails', '1');
  url.searchParams.set('accept-language', 'pl');
  url.searchParams.set('limit', '8');
  const response = await fetch(url.toString(), {
    signal,
    headers: { Accept: 'application/json' },
  });
  if (!response.ok) {
    throw new Error('Nie udało się pobrać podpowiedzi');
  }
  const data = (await response.json()) as NominatimResult[];
  const seen = new Set<string>();
  const places: PlaceSuggestion[] = [];
  for (const item of data) {
    const place = describePlace(item);
    const key = `${place.label}|${place.sublabel}`;
    if (seen.has(key)) {
      continue;
    }
    seen.add(key);
    places.push(place);
  }
  return places;
}

type ListingLocation = {
  city: string;
  district: string;
  estate: string;
  street: string;
  building: string;
  unit: string;
};

type ListingPhoto = {
  id: string;
  name: string;
  url: string;
};

const INITIAL_LISTING_LOCATION: ListingLocation = {
  city: '',
  district: '',
  estate: '',
  street: '',
  building: '',
  unit: '',
};

const INITIAL_LISTING_COORDS: LatLng = { lat: 50.0491, lng: 19.9617 };

function locationSearchQuery(location: ListingLocation) {
  return [location.street, location.building, location.estate, location.district, location.city, 'Polska']
    .filter((part) => part.trim())
    .join(', ');
}

function locationZoom(location: ListingLocation) {
  if (location.street && location.building) {
    return 17;
  }
  if (location.street || location.district || location.estate) {
    return 15;
  }
  return 12;
}

async function geocodeListingLocation(location: ListingLocation, signal: AbortSignal): Promise<LatLng | null> {
  const query = locationSearchQuery(location);
  if (!location.city.trim() || query.length < 4) {
    return null;
  }
  const url = new URL('https://nominatim.openstreetmap.org/search');
  url.searchParams.set('q', query);
  url.searchParams.set('format', 'jsonv2');
  url.searchParams.set('countrycodes', 'pl');
  url.searchParams.set('addressdetails', '1');
  url.searchParams.set('accept-language', 'pl');
  url.searchParams.set('limit', '1');
  const response = await fetch(url.toString(), { signal, headers: { Accept: 'application/json' } });
  if (!response.ok) {
    throw new Error('Nie udało się odszukać adresu');
  }
  const [result] = (await response.json()) as NominatimResult[];
  if (!result) {
    return null;
  }
  return { lat: Number(result.lat), lng: Number(result.lon) };
}

async function reverseGeocodeListingLocation(point: LatLng, signal: AbortSignal): Promise<Partial<ListingLocation>> {
  const url = new URL('https://nominatim.openstreetmap.org/reverse');
  url.searchParams.set('lat', String(point.lat));
  url.searchParams.set('lon', String(point.lng));
  url.searchParams.set('format', 'jsonv2');
  url.searchParams.set('addressdetails', '1');
  url.searchParams.set('accept-language', 'pl');
  url.searchParams.set('zoom', '18');
  const response = await fetch(url.toString(), { signal, headers: { Accept: 'application/json' } });
  if (!response.ok) {
    throw new Error('Nie udało się pobrać lokalizacji z mapy');
  }
  const data = (await response.json()) as NominatimResult;
  const address = data.address ?? {};
  const road = address.road ?? address.pedestrian ?? address.footway ?? address.path ?? address.cycleway ?? address.street ?? '';
  const locality = address.village ?? address.hamlet ?? address.locality ?? '';
  const street = road && !/^\d+[a-z]?$/i.test(road) ? road : locality;
  return {
    city: address.city ?? address.town ?? address.municipality ?? address.county?.replace(/^powiat\s+/i, '') ?? address.village ?? '',
    district: address.city_district ?? address.suburb ?? address.quarter ?? address.borough ?? address.district ?? '',
    estate: address.neighbourhood ?? address.residential ?? address.hamlet ?? '',
    street,
    building: address.house_number ?? '',
  };
}

const avatars = ['A', 'M', 'K', 'P', 'J'];

function getDisplayName(user: AuthUser | null): string {
  return user?.fullName?.trim() || user?.email || 'Użytkownik';
}

function getFirstName(fullName: string): string {
  const trimmed = fullName.trim();
  return trimmed.split(/\s+/)[0] || trimmed;
}

function getLastName(fullName: string): string {
  const parts = fullName.trim().split(/\s+/).filter(Boolean);
  return parts.slice(1).join(' ');
}

function getInitials(fullName: string): string {
  const parts = fullName.trim().split(/\s+/).filter(Boolean);
  if (parts.length === 0) {
    return '??';
  }
  if (parts.length === 1) {
    return parts[0].slice(0, 2).toUpperCase();
  }
  return `${parts[0][0]}${parts[1][0]}`.toUpperCase();
}

function contactPreferenceLabel(preference: ContactPreference | null | undefined): string {
  if (preference === 'EMAIL') {
    return 'E-mail';
  }
  if (preference === 'PHONE') {
    return 'Telefon';
  }
  return 'E-mail, Telefon';
}

function preferredLanguageLabel(language: PreferredLanguage | null | undefined): string {
  if (language === 'EN') {
    return 'Angielski';
  }
  if (language === 'UK') {
    return 'Ukraiński';
  }
  if (language === 'DE') {
    return 'Niemiecki';
  }
  return 'Polski';
}

function App() {
  const { user, logout } = useAuth();
  const navigate = useNavigate();
  const location = useLocation();
  const navigationType = useNavigationType();
  const [favoriteListingsState, setFavoriteListingsState] = useState<FavoriteListing[]>(favoriteListings);
  const [favoritesPreferredTab, setFavoritesPreferredTab] = useState<FavoriteTab>('buy');
  const [listingPriceAlertSubscriptionsState, setListingPriceAlertSubscriptionsState] = useState<ListingPriceAlertSubscription[]>([]);
  const [unreadFavoriteIds, setUnreadFavoriteIds] = useState<Set<string>>(new Set());
  const hydratedFavoriteIdsRef = useRef<Set<number>>(new Set());
  const favoriteListingIdList = useMemo(
    () => Array.from(new Set(favoriteListingsState.map((item) => item.listingId))).sort((left, right) => left - right),
    [favoriteListingsState],
  );
  const favoriteListingIdsSignature = useMemo(() => favoriteListingIdList.join(','), [favoriteListingIdList]);
  const listingPriceAlertIdList = useMemo(
    () => Array.from(new Set(listingPriceAlertSubscriptionsState.map((item) => item.listingId))).sort((left, right) => left - right),
    [listingPriceAlertSubscriptionsState],
  );
  const listingPriceAlertIdsSignature = useMemo(() => listingPriceAlertIdList.join(','), [listingPriceAlertIdList]);
  const monitoredListingIdList = useMemo(
    () => Array.from(new Set([...favoriteListingIdList, ...listingPriceAlertIdList])).sort((left, right) => left - right),
    [favoriteListingIdList, listingPriceAlertIdList],
  );
  const monitoredListingIdsSignature = useMemo(() => monitoredListingIdList.join(','), [monitoredListingIdList]);
  const translatedOriginalTextRef = useRef<Map<Text, string>>(new Map());
  const translatedOriginalAttrRef = useRef<Map<Element, Record<string, string>>>(new Map());
  const translationObserverRef = useRef<MutationObserver | null>(null);
  const translationTimerRef = useRef<number | null>(null);
  const translationInFlightRef = useRef(false);
  const filters = useFilterState();
  const notificationsCenter = useNotifications();
  const [savedSearchesState, setSavedSearchesState] = useState<SavedSearchRecord[]>([]);

  // Po udanym logowaniu wracamy tam, gdzie uzytkownik zmierzal (ProtectedRoute zapisuje cel
  // w location.state.from). Gdy wszedl na logowanie wprost - admin do panelu, uzytkownik do konta.
  const handleAuthenticated = (loggedUser: AuthUser) => {
    const from = (location.state as { from?: string } | null)?.from;
    const fallback = loggedUser.role === 'ADMIN' ? ROUTES.admin : ROUTES.account;
    navigate(from && from !== ROUTES.login ? from : fallback, { replace: true });
  };

  const handleLogout = () => {
    logout();
    navigate(ROUTES.home);
  };

  // Z kalkulatora zdolności: ustaw maksymalną cenę ofert na obliczony budżet i przejdź do wyszukiwarki.
  const handleSearchWithBudget = (maxPrice: number) => {
    if (maxPrice > 0) {
      filters.setCity('');
      filters.setCityInput('');
      filters.setPriceMin('');
      filters.setPriceMax(String(Math.round(maxPrice)));
    }
    navigate(ROUTES.buy);
  };

  useEffect(() => {
    if (!user) {
      setSavedSearchesState([]);
      return;
    }
    setSavedSearchesState(readUserJsonStorage<SavedSearchRecord[]>(user, SAVED_SEARCHES_STORAGE_SUFFIX, []));
  }, [user]);

  useEffect(() => {
    setFavoriteListingsState(readUserJsonStorage<FavoriteListing[]>(user, FAVORITE_LISTINGS_STORAGE_SUFFIX, []));
    setFavoritesPreferredTab(readUserJsonStorage<FavoriteTab>(user, FAVORITES_PREFERRED_TAB_STORAGE_SUFFIX, 'buy'));
    setListingPriceAlertSubscriptionsState(readUserJsonStorage<ListingPriceAlertSubscription[]>(user, LISTING_PRICE_ALERTS_STORAGE_SUFFIX, []));
  }, [user]);

  useEffect(() => {
    if (!user) {
      return;
    }
    writeUserJsonStorage(user, SAVED_SEARCHES_STORAGE_SUFFIX, savedSearchesState);
  }, [savedSearchesState, user]);

  useEffect(() => {
    writeUserJsonStorage(user, FAVORITE_LISTINGS_STORAGE_SUFFIX, favoriteListingsState);
  }, [favoriteListingsState, user]);

  useEffect(() => {
    writeUserJsonStorage(user, FAVORITES_PREFERRED_TAB_STORAGE_SUFFIX, favoritesPreferredTab);
  }, [favoritesPreferredTab, user]);

  useEffect(() => {
    writeUserJsonStorage(user, LISTING_PRICE_ALERTS_STORAGE_SUFFIX, listingPriceAlertSubscriptionsState);
  }, [listingPriceAlertSubscriptionsState, user]);

  useEffect(() => {
    if (!user) {
      return;
    }

    let cancelled = false;
    let running = false;

    const runDailyCityAlertsUpdate = async () => {
      if (cancelled || running) {
        return;
      }

      const todayKey = currentLocalDayKey();
      const lastRunKey = readUserJsonStorage<string | null>(user, CITY_PRICE_ALERT_LAST_RUN_STORAGE_SUFFIX, null);
      if (lastRunKey === todayKey) {
        return;
      }

      running = true;
      try {
        const subscriptions = readUserJsonStorage<CityPriceAlertSubscription[]>(user, CITY_PRICE_ALERTS_STORAGE_SUFFIX, []);
        if (subscriptions.length === 0) {
          writeUserJsonStorage(user, CITY_PRICE_ALERT_LAST_RUN_STORAGE_SUFFIX, todayKey);
          return;
        }

        const saleListings = await apiFetch<ApiListingSummary[]>('/listings?offerType=SALE');
        const previousSnapshots = readUserJsonStorage<Record<string, CityPriceAlertSnapshot>>(user, CITY_PRICE_ALERT_SNAPSHOTS_STORAGE_SUFFIX, {});
        const previousEvents = readUserJsonStorage<CityPriceAlertEvent[]>(user, CITY_PRICE_ALERT_EVENTS_STORAGE_SUFFIX, []);

        const nowIso = new Date().toISOString();
        const nextSnapshots: Record<string, CityPriceAlertSnapshot> = { ...previousSnapshots };
        const incomingEvents: CityPriceAlertEvent[] = [];

        subscriptions.forEach((subscription) => {
          const storageKey = cityAlertStorageKey(subscription);
          const currentPerM2 = calculateCityAlertPerM2(subscription, saleListings);
          const previousPerM2 = previousSnapshots[storageKey]?.perM2;

          if (Number.isFinite(previousPerM2) && previousPerM2 !== currentPerM2) {
            incomingEvents.push({
              id: `${storageKey}:${todayKey}:${previousPerM2}->${currentPerM2}`,
              subscriptionKey: subscription.key,
              label: subscription.label,
              city: subscription.city,
              region: subscription.region,
              propertyType: subscription.propertyType,
              marketType: subscription.marketType,
              previousPerM2,
              currentPerM2,
              changedAt: nowIso,
            });
          }

          nextSnapshots[storageKey] = {
            dayKey: todayKey,
            perM2: currentPerM2,
            updatedAt: nowIso,
          };
        });

        writeUserJsonStorage(user, CITY_PRICE_ALERT_SNAPSHOTS_STORAGE_SUFFIX, nextSnapshots);
        if (incomingEvents.length > 0) {
          const mergedEvents = mergeCityPriceAlertEvents(incomingEvents, previousEvents);
          writeUserJsonStorage(user, CITY_PRICE_ALERT_EVENTS_STORAGE_SUFFIX, mergedEvents);
        }
        writeUserJsonStorage(user, CITY_PRICE_ALERT_LAST_RUN_STORAGE_SUFFIX, todayKey);
      } catch {
        // Keep silent; updater retries on next interval.
      } finally {
        running = false;
      }
    };

    void runDailyCityAlertsUpdate();
    const intervalId = window.setInterval(() => {
      void runDailyCityAlertsUpdate();
    }, 60000);

    return () => {
      cancelled = true;
      window.clearInterval(intervalId);
    };
  }, [user]);

  useEffect(() => {
    if (location.pathname === ROUTES.favorites) {
      setUnreadFavoriteIds(new Set());
    }
  }, [location.pathname]);

  useEffect(() => {
    const preferred = user?.preferredLanguage ?? 'PL';
    const htmlLang = preferred === 'EN' ? 'en' : preferred === 'UK' ? 'uk' : preferred === 'DE' ? 'de' : 'pl';
    document.documentElement.lang = htmlLang;
  }, [user?.preferredLanguage]);

  useEffect(() => {
    const targetLanguage = user?.preferredLanguage ?? 'PL';
    const textNodeSources = translatedOriginalTextRef.current;
    const attrSources = translatedOriginalAttrRef.current;

    const restorePolish = () => {
      textNodeSources.forEach((source, node) => {
        if (node.isConnected) {
          node.textContent = source;
        }
      });
      attrSources.forEach((sourceAttrs, element) => {
        if (!element.isConnected) {
          return;
        }
        Object.entries(sourceAttrs).forEach(([attr, value]) => {
          element.setAttribute(attr, value);
        });
      });
    };

    const shouldTranslateSource = (value: string) => {
      const trimmed = value.trim();
      if (!trimmed) {
        return false;
      }
      // Skip pure numbers and separators.
      if (!/[a-zA-Z\u00C0-\u024F\u0400-\u04FF\u0104\u0105\u0106\u0107\u0118\u0119\u0141\u0142\u0143\u0144\u00D3\u00F3\u015A\u015B\u0179\u017A\u017B\u017C]/.test(trimmed)) {
        return false;
      }
      return true;
    };

    const collectTargets = () => {
      const textTargets: Array<{ node: Text; source: string }> = [];
      const attrTargets: Array<{ element: Element; attr: string; source: string }> = [];

      const root = document.querySelector('.site-shell') ?? document.body;
      const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
      let current = walker.nextNode();
      while (current) {
        const textNode = current as Text;
        const parent = textNode.parentElement;
        if (parent && !['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA'].includes(parent.tagName)) {
          const source = textNodeSources.get(textNode) ?? textNode.textContent ?? '';
          if (!textNodeSources.has(textNode)) {
            textNodeSources.set(textNode, source);
          }
          if (shouldTranslateSource(source)) {
            textTargets.push({ node: textNode, source });
          }
        }
        current = walker.nextNode();
      }

      root.querySelectorAll('[placeholder], [title], [aria-label]').forEach((element) => {
        const tracked = attrSources.get(element) ?? {};
        ['placeholder', 'title', 'aria-label'].forEach((attr) => {
          const raw = element.getAttribute(attr);
          if (!raw) {
            return;
          }
          const source = tracked[attr] ?? raw;
          if (!tracked[attr]) {
            tracked[attr] = source;
          }
          if (shouldTranslateSource(source)) {
            attrTargets.push({ element, attr, source });
          }
        });
        if (Object.keys(tracked).length > 0) {
          attrSources.set(element, tracked);
        }
      });

      return { textTargets, attrTargets };
    };

    const translateVisibleContent = async () => {
      if (targetLanguage === 'PL') {
        restorePolish();
        return;
      }
      if (translationInFlightRef.current) {
        return;
      }

      const { textTargets, attrTargets } = collectTargets();
      const sources = Array.from(new Set([
        ...textTargets.map((item) => item.source),
        ...attrTargets.map((item) => item.source),
      ]));

      if (sources.length === 0) {
        return;
      }

      translationInFlightRef.current = true;
      try {
        const mergedTranslations: Record<string, string> = {};
        const missingForApi: string[] = [];

        sources.forEach((source) => {
          const manual = getManualTranslation(targetLanguage, source);
          if (manual != null) {
            mergedTranslations[source] = manual;
          } else if (source.length <= 380) {
            missingForApi.push(source);
          } else {
            mergedTranslations[source] = source;
          }
        });

        if (missingForApi.length > 0) {
          const fallbackChunks: string[][] = [];
          for (let index = 0; index < missingForApi.length; index += 150) {
            fallbackChunks.push(missingForApi.slice(index, index + 150));
          }

          for (const chunk of fallbackChunks) {
            const response = await apiFetch<{ language: PreferredLanguage; translations: Record<string, string> }>('/i18n/translate', {
              method: 'POST',
              body: JSON.stringify({ language: targetLanguage, texts: chunk }),
            });
            Object.assign(mergedTranslations, response.translations);
          }
        }

        textTargets.forEach(({ node, source }) => {
          if (!node.isConnected) {
            return;
          }
          const translated = mergedTranslations[source];
          if (translated && node.textContent !== translated) {
            node.textContent = translated;
          }
        });

        attrTargets.forEach(({ element, attr, source }) => {
          if (!element.isConnected) {
            return;
          }
          const translated = mergedTranslations[source];
          if (translated) {
            element.setAttribute(attr, translated);
          }
        });
      } catch {
        // If translation fails, leave original text untouched.
      } finally {
        translationInFlightRef.current = false;
      }
    };

    const queueTranslate = () => {
      if (translationTimerRef.current != null) {
        window.clearTimeout(translationTimerRef.current);
      }
      translationTimerRef.current = window.setTimeout(() => {
        void translateVisibleContent();
      }, 80);
    };

    translationObserverRef.current?.disconnect();
    if (targetLanguage === 'PL') {
      restorePolish();
      return;
    }

    queueTranslate();
    const observer = new MutationObserver(() => {
      queueTranslate();
    });
    observer.observe(document.body, {
      childList: true,
      subtree: true,
      characterData: true,
      attributes: true,
      attributeFilter: ['placeholder', 'title', 'aria-label'],
    });
    translationObserverRef.current = observer;

    return () => {
      observer.disconnect();
      translationObserverRef.current = null;
      if (translationTimerRef.current != null) {
        window.clearTimeout(translationTimerRef.current);
        translationTimerRef.current = null;
      }
    };
  }, [user?.preferredLanguage, location.pathname]);

  // Miasto i typ oferty trafiaja do query stringa - wynik na mapie da sie udostepnic linkiem.
  const openMap = (name: string, source: 'buy' | 'rent' = 'buy') => {
    navigate(mapPath(name, source === 'rent' ? 'RENT' : 'SALE'));
  };

  // Wybrana oferta to dane przejsciowe miedzy trasami - przekazujemy je w location.state,
  // dzieki czemu nie zostaja w globalnym stanie po opuszczeniu widoku.
  const openNegotiation = (offer: BuyOffer) => {
    navigate(negotiationPath(), { state: { offer } });
  };

  const openListing = (id: number) => {
    navigate(listingPath(id));
  };

  const openMessagesWithTarget = (target: MessageThreadTarget) => {
    navigate(ROUTES.messages, { state: { threadTarget: target } });
  };

  // Klik w powiadomienie: oznacz jako przeczytane (znika z dzwonka, licznik -1) i przejdź do właściwego miejsca.
  const handleOpenNotification = (notification: ServerNotification | DisplayNotification) => {
    notificationsCenter.markRead(notification.id);
    const link = notification.link;
    if (!link) {
      navigate(ROUTES.notifications);
      return;
    }
    if (link.startsWith('listing:')) {
      const id = Number(link.slice('listing:'.length));
      if (Number.isFinite(id) && id > 0) {
        openListing(id);
        return;
      }
    }
    if (link.startsWith('messages')) {
      navigate(ROUTES.messages);
      return;
    }
    if (link === 'priceAlerts') {
      navigate(ROUTES.accountPriceAlerts);
      return;
    }
    if (link === 'meetings') {
      navigate(ROUTES.accountMeetings);
      return;
    }
    if (link === 'savedSearches') {
      navigate(ROUTES.accountSearches);
      return;
    }
    if (link === 'favorites') {
      navigate(ROUTES.favorites);
      return;
    }
    if (link === 'accountSecurity' || link === 'account' || link === 'accountListings') {
      navigate(ROUTES[link]);
      return;
    }
    navigate(ROUTES.notifications);
  };

  const toggleSavedSearch = (mode: SavedSearchMode) => {
    if (!user) {
      navigate(ROUTES.login);
      return;
    }

    const draft = buildSavedSearchDraft(mode, filters);
    setSavedSearchesState((current) => {
      const existing = current.find((item) => sameSavedSearchCriteria(item, draft));
      if (existing) {
        return current.filter((item) => item.id !== existing.id);
      }
      const nowIso = new Date().toISOString();
      const next: SavedSearchRecord = {
        id: `saved-search-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
        createdAt: nowIso,
        updatedAt: nowIso,
        ...draft,
      };
      return [next, ...current].slice(0, SAVED_SEARCHES_LIMIT);
    });
  };

  const openSavedSearch = (savedSearch: SavedSearchRecord) => {
    applySavedSearchToFilters(filters, savedSearch);
    navigate(savedSearch.mode === 'rent' ? ROUTES.rent : ROUTES.buy);
  };

  const removeSavedSearch = (savedSearchId: string) => {
    setSavedSearchesState((current) => current.filter((item) => item.id !== savedSearchId));
  };

  const buySearchSaved = savedSearchesState.some((item) => sameSavedSearchCriteria(item, buildSavedSearchDraft('buy', filters)));
  const rentSearchSaved = savedSearchesState.some((item) => sameSavedSearchCriteria(item, buildSavedSearchDraft('rent', filters)));

  const isListingFavorite = (listingId: number) => (
    favoriteListingsState.some((item) => item.id === favoriteListingKey(listingId))
  );

  const toggleListingFavorite = (listing: ApiListingDetail) => {
    const listingKey = favoriteListingKey(listing.id);
    const alreadyFavorite = favoriteListingsState.some((item) => item.id === listingKey);
    if (!alreadyFavorite) {
      notificationsCenter.notify({
        category: 'listing',
        icon: 'heart',
        title: 'Ogłoszenie zapisane',
        body: `„${listing.title}” zostało zapisane do ulubionych.`,
        link: `listing:${listing.id}`,
        dedupeKey: `favorite-${listing.id}`,
      });
    }
    setFavoriteListingsState((current) => {
      const exists = current.some((item) => item.id === listingKey);
      if (exists) {
        setUnreadFavoriteIds((prev) => {
          if (!prev.has(listingKey)) {
            return prev;
          }
          const next = new Set(prev);
          next.delete(listingKey);
          return next;
        });
        return current.filter((item) => item.id !== listingKey);
      }
      setFavoritesPreferredTab(favoriteTabFromOfferType(listing.offerType));
      setUnreadFavoriteIds((prev) => {
        const next = new Set(prev);
        next.add(listingKey);
        return next;
      });
      return [toFavoriteListing(listing), ...current];
    });
  };

  const isListingPriceAlertEnabled = (listingId: number) => (
    listingPriceAlertSubscriptionsState.some((item) => item.listingId === listingId)
  );

  const toggleListingPriceAlert = (listing: ApiListingDetail) => {
    const listingLocation = [listing.city, listing.district].filter(Boolean).join(', ');
    setListingPriceAlertSubscriptionsState((current) => {
      const exists = current.some((item) => item.listingId === listing.id);
      if (exists) {
        return current.filter((item) => item.listingId !== listing.id);
      }
      const next: ListingPriceAlertSubscription = {
        listingId: listing.id,
        title: listing.title,
        location: listingLocation || listing.city,
        offerType: listing.offerType,
        createdAt: new Date().toISOString(),
      };
      return [next, ...current];
    });
  };

  const toggleSummaryFavorite = async (listing: ApiListingSummary) => {
    const listingKey = favoriteListingKey(listing.id);
    const exists = favoriteListingsState.some((item) => item.id === listingKey);
    if (exists) {
      setUnreadFavoriteIds((prev) => {
        if (!prev.has(listingKey)) {
          return prev;
        }
        const next = new Set(prev);
        next.delete(listingKey);
        return next;
      });
      setFavoriteListingsState((current) => current.filter((item) => item.id !== listingKey));
      return;
    }

    setFavoritesPreferredTab(favoriteTabFromOfferType(listing.offerType));

    try {
      const fullListing = await apiFetch<ApiListingDetail>(`/listings/${listing.id}`);
      const nextFavorite = toFavoriteListing(fullListing);
      setFavoriteListingsState((current) => {
        if (current.some((item) => item.id === listingKey)) {
          return current;
        }
        setUnreadFavoriteIds((prev) => {
          const next = new Set(prev);
          next.add(listingKey);
          return next;
        });
        return [nextFavorite, ...current];
      });
    } catch {
      const fallbackFavorite = toFavoriteListingFromSummary(listing);
      setFavoriteListingsState((current) => {
        if (current.some((item) => item.id === listingKey)) {
          return current;
        }
        setUnreadFavoriteIds((prev) => {
          const next = new Set(prev);
          next.add(listingKey);
          return next;
        });
        return [fallbackFavorite, ...current];
      });
    }
  };

  useEffect(() => {
    const toHydrate = favoriteListingsState
      .filter((item) => item.photos <= 1 && !hydratedFavoriteIdsRef.current.has(item.listingId))
      .map((item) => item.listingId);

    if (toHydrate.length === 0) {
      return;
    }

    toHydrate.forEach((listingId) => hydratedFavoriteIdsRef.current.add(listingId));
    let cancelled = false;

    Promise.all(
      toHydrate.map(async (listingId) => {
        try {
          return await apiFetch<ApiListingDetail>(`/listings/${listingId}`);
        } catch {
          return null;
        }
      }),
    ).then((resolved) => {
      if (cancelled) {
        return;
      }

      const hydratedById = new Map<number, FavoriteListing>();
      resolved.forEach((listing) => {
        if (listing) {
          hydratedById.set(listing.id, toFavoriteListing(listing));
        }
      });

      if (hydratedById.size === 0) {
        return;
      }

      setFavoriteListingsState((current) => current.map((item) => {
        const hydrated = hydratedById.get(item.listingId);
        if (!hydrated) {
          return item;
        }
        return {
          ...item,
          ...hydrated,
          id: item.id,
          listingId: item.listingId,
        };
      }));
    });

    return () => {
      cancelled = true;
    };
  }, [favoriteListingsState]);

  useEffect(() => {
    if (!user || monitoredListingIdList.length === 0) {
      return;
    }

    let cancelled = false;
    let snapshots = readUserJsonStorage<Record<string, number>>(user, FAVORITE_PRICE_SNAPSHOTS_STORAGE_SUFFIX, {});
    const alertListingIdSet = new Set(listingPriceAlertIdList);

    const syncFavoritePrices = async () => {
      const refreshed = await Promise.all(
        monitoredListingIdList.map(async (listingId) => {
          try {
            return await apiFetch<ApiListingDetail>(`/listings/${listingId}`);
          } catch {
            return null;
          }
        }),
      );

      if (cancelled) {
        return;
      }

      const nowIso = new Date().toISOString();
      const incomingEvents: FavoritePriceAlertEvent[] = [];
      const nextSnapshots = { ...snapshots };
      const refreshedFavorites = new Map<number, FavoriteListing>();

      refreshed.forEach((listing) => {
        if (!listing) {
          return;
        }

        const listingIdKey = String(listing.id);
        const previousPrice = nextSnapshots[listingIdKey];
        const currentPrice = Number(listing.price);
        if (Number.isFinite(currentPrice)) {
          if (alertListingIdSet.has(listing.id) && Number.isFinite(previousPrice) && previousPrice !== currentPrice) {
            const location = [listing.city, listing.district].filter(Boolean).join(', ');
            incomingEvents.push({
              id: `${listing.id}:${previousPrice}->${currentPrice}`,
              listingId: listing.id,
              title: listing.title,
              location: location || listing.city,
              area: `${`${listing.area}`.replace('.', ',')} m²`,
              offerType: listing.offerType,
              previousPrice,
              currentPrice,
              changedAt: nowIso,
            });
          }
          nextSnapshots[listingIdKey] = currentPrice;
        }

        refreshedFavorites.set(listing.id, toFavoriteListing(listing));
      });

      snapshots = nextSnapshots;
      writeUserJsonStorage(user, FAVORITE_PRICE_SNAPSHOTS_STORAGE_SUFFIX, nextSnapshots);

      if (incomingEvents.length > 0) {
        const existingEvents = readUserJsonStorage<FavoritePriceAlertEvent[]>(user, FAVORITE_PRICE_ALERTS_STORAGE_SUFFIX, []);
        const merged = mergePriceAlertEvents(incomingEvents, existingEvents);
        writeUserJsonStorage(user, FAVORITE_PRICE_ALERTS_STORAGE_SUFFIX, merged);

        // Powiadomienie tylko o spadku ceny obserwowanego ogloszenia.
        incomingEvents.forEach((event) => {
          if (event.currentPrice < event.previousPrice) {
            const diff = event.previousPrice - event.currentPrice;
            notificationsCenter.notify({
              category: 'listing',
              icon: 'coin',
              title: 'Zmiana ceny',
              body: `Cena „${event.title}” spadła o ${diff.toLocaleString('pl-PL')} zł.`,
              link: `listing:${event.listingId}`,
              dedupeKey: `price-${event.listingId}-${event.currentPrice}`,
            });
          }
        });
      }

      if (refreshedFavorites.size > 0) {
        setFavoriteListingsState((current) => current.map((item) => {
          const refreshedItem = refreshedFavorites.get(item.listingId);
          if (!refreshedItem) {
            return item;
          }
          return {
            ...item,
            ...refreshedItem,
            id: item.id,
            listingId: item.listingId,
          };
        }));
      }
    };

    void syncFavoritePrices();
    const intervalId = window.setInterval(() => {
      void syncFavoritePrices();
    }, 45000);

    return () => {
      cancelled = true;
      window.clearInterval(intervalId);
    };
  }, [user, monitoredListingIdList, monitoredListingIdsSignature, listingPriceAlertIdList, listingPriceAlertIdsSignature]);

  // Wejscie na nowa trase zaczynamy od gory strony. Przy cofaniu (POP) zostawiamy
  // pozycje przegladarce, zeby powrot do listy ofert wracal w to samo miejsce.
  useEffect(() => {
    if (navigationType !== 'POP') {
      window.scrollTo({ top: 0, behavior: 'auto' });
    }
  }, [location.pathname, navigationType]);

  return (
    <div className={`site-shell ${location.pathname === ROUTES.login ? 'login-shell' : ''}`}>
      <Header onLogout={handleLogout} hasUnreadFavorites={unreadFavoriteIds.size > 0} onOpenNotification={handleOpenNotification} />
      <main>
        <Routes>
          {/* Trasy publiczne */}
          <Route path={ROUTES.home} element={<HomePage filters={filters} onOpenMap={openMap} />} />
          <Route path={ROUTES.login} element={<LoginPage onLogin={handleAuthenticated} />} />
          <Route
            path={ROUTES.buy}
            element={(
              <BuyPage
                filters={filters}
                onOpenMap={openMap}
                onOpenNegotiation={openNegotiation}
                onOpenListing={openListing}
                isFavorite={isListingFavorite}
                onToggleFavorite={toggleSummaryFavorite}
                savedSearchActive={buySearchSaved}
                onToggleSavedSearch={() => toggleSavedSearch('buy')}
              />
            )}
          />
          <Route
            path={ROUTES.rent}
            element={(
              <RentPage
                filters={filters}
                onOpenMap={openMap}
                onOpenListing={openListing}
                isFavorite={isListingFavorite}
                onToggleFavorite={toggleSummaryFavorite}
                savedSearchActive={rentSearchSaved}
                onToggleSavedSearch={() => toggleSavedSearch('rent')}
              />
            )}
          />
          <Route
            path={ROUTES.listingDetail}
            element={(
              <ListingDetailRoute
                onStartChat={openMessagesWithTarget}
                isFavorite={isListingFavorite}
                onToggleFavorite={toggleListingFavorite}
                isListingAlertEnabled={isListingPriceAlertEnabled}
                onToggleListingAlert={toggleListingPriceAlert}
              />
            )}
          />
          <Route path={ROUTES.map} element={<MapSearchRoute />} />
          <Route path={ROUTES.sell} element={<SellPage />} />
          <Route path={ROUTES.valuation} element={<ValuationPage />} />
          <Route path={ROUTES.creditCalculator} element={<CreditCalculatorPage onSearchWithBudget={handleSearchWithBudget} />} />
          <Route path={ROUTES.priceHistory} element={<PriceHistoryPage user={user} />} />
          <Route path={ROUTES.districtRanking} element={<DistrictRankingPage />} />
          <Route path={ROUTES.negotiation} element={<NegotiationRoute />} />
          <Route path={ROUTES.services} element={<ServicesPage />} />
          <Route path={ROUTES.companies} element={<CompaniesPage />} />
          <Route path={ROUTES.guides} element={<GuidesPage />} />
          <Route path={ROUTES.guideBuying} element={<GuideBuyingPage />} />

          {/* Trasy wymagajace zalogowania */}
          <Route element={<ProtectedRoute />}>
            <Route path={ROUTES.account} element={<AccountDashboardPage savedSearches={savedSearchesState} onOpenSavedSearch={openSavedSearch} />} />
            <Route
              path={ROUTES.accountSearches}
              element={(
                <AccountSavedSearchesPage
                  savedSearches={savedSearchesState}
                  onOpenSavedSearch={openSavedSearch}
                  onRemoveSavedSearch={removeSavedSearch}
                />
              )}
            />
            <Route path={ROUTES.accountPriceAlerts} element={<AccountPriceAlertsPage user={user} onOpenListing={openListing} />} />
            <Route path={ROUTES.comparison} element={<AccountComparisonPage onOpenListing={openListing} />} />
            <Route path={ROUTES.accountMeetings} element={<AccountMeetingsPage />} />
            <Route path={ROUTES.accountListings} element={<AccountListingsPage onOpenListing={openListing} />} />
            <Route path={ROUTES.accountSettings} element={<AccountSettingsPage />} />
            <Route path={ROUTES.accountSecurity} element={<AccountSecurityPage />} />
            <Route path={ROUTES.accountProfileEdit} element={<AccountProfileEditPage />} />
            <Route path={ROUTES.accountPublicProfile} element={<AccountPublicProfilePage />} />
            <Route path={ROUTES.accountHelpContact} element={<AccountHelpContactPage user={user} />} />
            <Route path={ROUTES.notifications} element={<NotificationsPage onOpenNotification={handleOpenNotification} />} />
            <Route path={ROUTES.messages} element={<MessagesPage onOpenListing={openListing} />} />
            <Route path={ROUTES.add} element={<AddListingPage />} />
            <Route
              path={ROUTES.favorites}
              element={(
                <FavoritesPage
                  onOpenListing={openListing}
                  onStartChat={openMessagesWithTarget}
                  favoritesState={favoriteListingsState}
                  setFavoritesState={setFavoriteListingsState}
                  preferredTab={favoritesPreferredTab}
                />
              )}
            />
          </Route>

          {/* Panel administratora - zakladki jako podsciezki /admin/* */}
          <Route element={<ProtectedRoute requireAdmin />}>
            <Route path={`${ROUTES.admin}/*`} element={<AdminPage />} />
          </Route>

          <Route path="*" element={<NotFoundPage />} />
        </Routes>
      </main>
    </div>
  );
}

// Szczegoly oferty: identyfikator pochodzi z adresu (/oferta/:id), nie ze stanu Reacta.
// Dzieki temu link do oferty da sie wyslac, odswiezyc i otworzyc w nowej karcie.
function ListingDetailRoute(props: {
  onStartChat: (target: MessageThreadTarget) => void;
  isFavorite: (listingId: number) => boolean;
  onToggleFavorite: (listing: ApiListingDetail) => void;
  isListingAlertEnabled: (listingId: number) => boolean;
  onToggleListingAlert: (listing: ApiListingDetail) => void;
}) {
  const { id } = useParams<{ id: string }>();
  const listingId = Number(id);

  if (!Number.isFinite(listingId) || listingId <= 0) {
    return <NotFoundPage />;
  }

  return <ListingDetailPage listingId={listingId} {...props} />;
}

// Mapa czyta miasto i typ oferty z query stringa (/mapa?miasto=Krakow&typ=wynajem).
function MapSearchRoute() {
  const [searchParams] = useSearchParams();
  const city = searchParams.get('miasto') ?? '';
  const offerType: ApiOfferType = searchParams.get('typ') === 'wynajem' ? 'RENT' : 'SALE';

  return <MapSearchPage key={`${city}-${offerType}`} initialCity={city} offerType={offerType} />;
}

// Negocjacje: oferta wskazana z listy ofert przychodzi w location.state (dane przejsciowe),
// wybor z rozwijanej listy to juz zwykly stan formularza.
function NegotiationRoute() {
  const location = useLocation();
  const offerFromState = (location.state as { offer?: BuyOffer } | null)?.offer ?? null;
  const [selectedOffer, setSelectedOffer] = useState<BuyOffer | null>(offerFromState ?? buyListings[0] ?? null);

  useEffect(() => {
    if (offerFromState) {
      setSelectedOffer(offerFromState);
    }
  }, [offerFromState]);

  return <NegotiationPage offers={buyListings} selectedOffer={selectedOffer} onSelectOffer={setSelectedOffer} />;
}

// Nieznany adres: czytelny komunikat zamiast pustego ekranu.
function NotFoundPage() {
  return (
    <section className="not-found-page" aria-label="Nie znaleziono strony">
      <div className="not-found-shell">
        <h1>404</h1>
        <h2>Nie znaleźliśmy tej strony</h2>
        <p>Adres jest nieprawidłowy albo strona została przeniesiona.</p>
        <div className="not-found-actions">
          <Link className="not-found-primary" to={ROUTES.home}>Wróć na stronę główną</Link>
          <Link className="not-found-secondary" to={ROUTES.buy}>Przeglądaj oferty</Link>
        </div>
      </div>
    </section>
  );
}

type AdminListing = {
  id: number;
  title: string;
  city: string;
  offerType: 'SALE' | 'RENT';
  propertyType: 'APARTMENT' | 'HOUSE';
  price: number;
  area: number;
  rooms: number;
  ownerEmail: string | null;
  status: 'PENDING' | 'APPROVED' | 'REJECTED';
  createdAt: string;
};

type AdminListingReportAttachment = {
  fileName: string;
  fileType: string | null;
  dataUrl: string | null;
};

type AdminListingReport = {
  id: number;
  listingId: number;
  listingTitle: string;
  listingCity: string;
  listingOwnerEmail: string | null;
  reasonId: string;
  reasonTitle: string;
  details: string | null;
  attachmentNames: string[];
  attachments?: AdminListingReportAttachment[];
  reporterEmail: string;
  reporterName: string | null;
  status: 'OPEN' | 'RESOLVED';
  listingDeleted: boolean;
  createdAt: string;
  resolvedAt: string | null;
  resolvedByEmail: string | null;
  resolutionNote: string | null;
};

type ForbiddenWord = {
  id: number;
  word: string;
};

type AdminResetPasswordResponse = {
  temporaryPassword: string;
};

type AdminStatsResponse = {
  periodDays: number;
  generatedAt: string;
  summary: {
    activeListings: number;
    pendingListings: number;
    totalUsers: number;
    pendingVerificationUsers: number;
    openReports: number;
    messagesToAdminTotal: number;
    blockedUsers: number;
  };
  period: {
    messagesToAdmin: number;
    newListings: number;
    newReports: number;
    newPersonalAccounts: number;
    newCompanyAccounts: number;
  };
  daily: {
    date: string;
    messagesToAdmin: number;
    newListings: number;
    newReports: number;
    newPersonalAccounts: number;
    newCompanyAccounts: number;
  }[];
};

const ADMIN_STATS_PERIOD_OPTIONS = [
  { days: 1, label: 'Dziś' },
  { days: 7, label: '7 dni' },
  { days: 14, label: '14 dni' },
  { days: 30, label: '30 dni' },
] as const;

type AdminStatsPeriodMode = 'preset' | 'custom';

const ADMIN_STATUS_LABELS: Record<AdminListing['status'], string> = {
  PENDING: 'Oczekuje',
  APPROVED: 'Zatwierdzone',
  REJECTED: 'Odrzucone',
};

const ADMIN_REPORT_STATUS_LABELS: Record<AdminListingReport['status'], string> = {
  OPEN: 'Otwarte',
  RESOLVED: 'Rozpatrzone',
};

const ADMIN_OFFER_LABELS: Record<AdminListing['offerType'], string> = {
  SALE: 'Sprzedaż',
  RENT: 'Wynajem',
};

const ADMIN_PROPERTY_LABELS: Record<AdminListing['propertyType'], string> = {
  APARTMENT: 'Mieszkanie',
  HOUSE: 'Dom',
};

function formatAdminDate(iso: string): string {
  const date = new Date(iso);
  if (Number.isNaN(date.getTime())) {
    return '—';
  }
  return date.toLocaleDateString('pl-PL', { day: '2-digit', month: '2-digit', year: 'numeric' });
}
function toDateInputValue(date: Date): string {
  const year = date.getFullYear();
  const month = `${date.getMonth() + 1}`.padStart(2, '0');
  const day = `${date.getDate()}`.padStart(2, '0');
  return `${year}-${month}-${day}`;
}
type AdminStatsQuickRange = '' | 'today' | 'last7' | 'last14' | 'last30';

function accountStorageKey(suffix: string, user: AuthUser | null): string {
  return `accountProfile:${user?.id ?? 'guest'}:${suffix}`;
}

// Zakladki panelu maja odpowiedniki w sciezkach /admin/* - jedno zrodlo prawdy w routes.ts.
type AdminTab = AdminTabKey;

const ADMIN_TAB_META: Record<AdminTab, { title: string; subtitle: string }> = {
  dashboard: { title: 'Panel administratora', subtitle: 'Zarządzaj ogłoszeniami i użytkownikami serwisu.' },
  listings: { title: 'Ogłoszenia', subtitle: 'Przeglądaj, zatwierdzaj i usuwaj ogłoszenia użytkowników.' },
  users: { title: 'Użytkownicy', subtitle: 'Zarządzaj kontami zarejestrowanych użytkowników.' },
  messages: { title: 'Wiadomości', subtitle: 'Podgląd wiadomości wymienianych między użytkownikami.' },
  reports: { title: 'Zgłoszenia', subtitle: 'Zgłoszenia dotyczące ogłoszeń i użytkowników wymagające uwagi.' },
  payments: { title: 'Płatności', subtitle: 'Historia płatności i promocji ogłoszeń.' },
  stats: { title: 'Statystyki', subtitle: 'Statystyki aktywności i ruchu w serwisie.' },
  settings: { title: 'Ustawienia serwisu', subtitle: 'Globalna konfiguracja serwisu Mieszko.' },
  moderation: { title: 'Weryfikacja ogłoszeń', subtitle: 'Kolejka ogłoszeń oczekujących na moderację.' },
  categories: { title: 'Kategorie', subtitle: 'Zarządzaj kategoriami i typami nieruchomości.' },
  locations: { title: 'Lokalizacje', subtitle: 'Zarządzaj obsługiwanymi miastami i dzielnicami.' },
  promotions: { title: 'Banery i promocje', subtitle: 'Zarządzaj banerami reklamowymi i promocjami.' },
  forbiddenWords: { title: 'Zakazane słowa', subtitle: 'Lista wszystkich niedozwolonych słów używanych przez globalny filtr treści.' },
  leads: { title: 'Leady', subtitle: 'Baza kontaktów marketingowych i grupy docelowe (dynamiczne segmenty).' },
  campaigns: { title: 'Kampanie marketingowe', subtitle: 'Twórz i wysyłaj kampanie e-mail oraz SMS do grup docelowych.' },
  mailConfig: { title: 'Konfiguracja wysyłki', subtitle: 'Ustawienia poczty SMTP i bramki SMS dla kampanii i powiadomień.' },
};

const ADMIN_NAV_ITEMS: { id: AdminTab; icon: string; label: string }[] = [
  { id: 'dashboard', icon: 'house', label: 'Panel główny' },
  { id: 'listings', icon: 'document', label: 'Ogłoszenia' },
  { id: 'users', icon: 'user', label: 'Użytkownicy' },
  { id: 'messages', icon: 'message', label: 'Wiadomości' },
  { id: 'reports', icon: 'warning', label: 'Zgłoszenia' },
  { id: 'leads', icon: 'user', label: 'Leady' },
  { id: 'campaigns', icon: 'mail', label: 'Kampanie marketingowe' },
  { id: 'payments', icon: 'credit-card', label: 'Płatności' },
  { id: 'stats', icon: 'chart', label: 'Statystyki' },
  { id: 'settings', icon: 'gear', label: 'Ustawienia serwisu' },
];

const ADMIN_TOOL_ITEMS: { id: AdminTab; icon: string; label: string }[] = [
  { id: 'moderation', icon: 'check', label: 'Weryfikacja ogłoszeń' },
  { id: 'mailConfig', icon: 'gear', label: 'Konfiguracja wysyłki' },
  { id: 'forbiddenWords', icon: 'shield', label: 'Zakazane słowa' },
  { id: 'categories', icon: 'list', label: 'Kategorie' },
  { id: 'locations', icon: 'pin', label: 'Lokalizacje' },
  { id: 'promotions', icon: 'spark', label: 'Banery i promocje' },
];

function AdminPlaceholderCard({ icon, label }: { icon: string; label: string }) {
  return (
    <div className="admin-card admin-placeholder">
      <span><Icon name={icon} /></span>
      <strong>{label}</strong>
      <p>Ta sekcja jest w przygotowaniu i pojawi się w kolejnej aktualizacji panelu.</p>
    </div>
  );
}

type AdminMessage = {
  id: number;
  senderId: number;
  recipientId: number;
  content: string;
  createdAt: string;
  mine: boolean;
};

type AdminMessageThread = {
  user: AuthUser;
  lastMessage: AdminMessage;
  totalMessages: number;
  awaitingAdminReply: number;
};

function AdminMessageModal({ user, onClose }: { user: AuthUser; onClose: () => void }) {
  const [messages, setMessages] = useState<AdminMessage[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [draft, setDraft] = useState('');
  const [sending, setSending] = useState(false);
  const listRef = useRef<HTMLDivElement | null>(null);

  const loadMessages = useCallback(async (silent = false) => {
    if (!silent) {
      setLoading(true);
    }
    setError(null);
    try {
      const data = await apiFetch<AdminMessage[]>(`/admin/users/${user.id}/messages`);
      setMessages(data);
    } catch (err) {
      if (!silent) {
        setError(err instanceof Error ? err.message : 'Nie udało się pobrać wiadomości.');
      }
    } finally {
      if (!silent) {
        setLoading(false);
      }
    }
  }, [user.id]);

  useEffect(() => {
    loadMessages();
  }, [loadMessages]);

  useEffect(() => {
    const intervalId = window.setInterval(() => {
      loadMessages(true);
    }, 4000);
    return () => window.clearInterval(intervalId);
  }, [loadMessages]);

  useEffect(() => {
    if (listRef.current) {
      listRef.current.scrollTop = listRef.current.scrollHeight;
    }
  }, [messages]);

  const handleSend = async () => {
    const content = draft.trim();
    if (!content || sending) {
      return;
    }
    setSending(true);
    setError(null);
    try {
      const sent = await apiFetch<AdminMessage>(`/admin/users/${user.id}/messages`, {
        method: 'POST',
        body: JSON.stringify({ content }),
      });
      setMessages((current) => [...current, sent]);
      setDraft('');
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Nie udało się wysłać wiadomości.');
    } finally {
      setSending(false);
    }
  };

  return (
    <div className="listing-delete-overlay" role="dialog" aria-modal="true" aria-label="Wiadomość do użytkownika">
      <div className="admin-message-modal">
        <button type="button" className="listing-delete-close" aria-label="Zamknij" onClick={onClose}>×</button>
        <h2>Wiadomość do: {user.fullName || user.email}</h2>
        <p className="admin-verify-subtitle">{user.email}</p>
        <button type="button" className="admin-message-refresh" onClick={() => loadMessages()}>
          Odśwież
        </button>

        {error && <p className="admin-message-error">{error}</p>}

        <div className="admin-message-list" ref={listRef}>
          {loading ? (
            <p className="admin-empty">Wczytywanie wiadomości...</p>
          ) : messages.length === 0 ? (
            <p className="admin-empty">Brak wiadomości. Napisz pierwszą wiadomość do tego użytkownika.</p>
          ) : (
            messages.map((message) => (
              <div key={message.id} className={message.mine ? 'admin-message-bubble mine' : 'admin-message-bubble'}>
                <p>{message.content}</p>
                <small>{new Date(message.createdAt).toLocaleString('pl-PL', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' })}</small>
              </div>
            ))
          )}
        </div>

        <div className="admin-message-composer">
          <textarea
            value={draft}
            onChange={(event) => setDraft(event.target.value)}
            placeholder="Napisz wiadomość do użytkownika..."
            rows={3}
          />
          <button type="button" className="admin-btn approve" disabled={sending || !draft.trim()} onClick={handleSend}>
            {sending ? 'Wysyłanie...' : 'Wyślij'}
          </button>
        </div>
      </div>
    </div>
  );
}

function AdminPage() {
  const { user } = useAuth();
  // Aktywna zakladka wynika z adresu (/admin/leady), nie ze stanu komponentu -
  // dzieki temu kazda sekcja panelu ma wlasny URL, dziala F5 i przycisk Wstecz.
  const adminSegment = useParams()['*'] ?? '';
  const tab: AdminTab = ADMIN_PATH_TO_TAB[adminSegment] ?? 'dashboard';
  const isUnknownAdminSection = adminSegment !== '' && !(adminSegment in ADMIN_PATH_TO_TAB);
  const [users, setUsers] = useState<AuthUser[]>([]);
  const [listings, setListings] = useState<AdminListing[]>([]);
  const [reports, setReports] = useState<AdminListingReport[]>([]);
  const [forbiddenWords, setForbiddenWords] = useState<ForbiddenWord[]>([]);
  const [newForbiddenWord, setNewForbiddenWord] = useState('');
  const [savingForbiddenWord, setSavingForbiddenWord] = useState(false);
  const [forbiddenSearch, setForbiddenSearch] = useState('');
  const [userSearch, setUserSearch] = useState('');
  const [deletingWordId, setDeletingWordId] = useState<number | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [busyId, setBusyId] = useState<string | null>(null);
  const [verificationUser, setVerificationUser] = useState<AuthUser | null>(null);
  const [messageUser, setMessageUser] = useState<AuthUser | null>(null);
  const [previewListingId, setPreviewListingId] = useState<number | null>(null);
  const [detailsReport, setDetailsReport] = useState<AdminListingReport | null>(null);
  const [messageThreads, setMessageThreads] = useState<AdminMessageThread[]>([]);
  const [messageThreadsLoading, setMessageThreadsLoading] = useState(false);
  const [messageThreadsError, setMessageThreadsError] = useState<string | null>(null);
  const [statsPeriodDays, setStatsPeriodDays] = useState<number>(1);
  const [statsPeriodMode, setStatsPeriodMode] = useState<AdminStatsPeriodMode>('preset');
  const [statsCustomFrom, setStatsCustomFrom] = useState<string>('');
  const [statsCustomTo, setStatsCustomTo] = useState<string>('');
  const [statsQuickRange, setStatsQuickRange] = useState<AdminStatsQuickRange>('');
  const [adminStats, setAdminStats] = useState<AdminStatsResponse | null>(null);
  const [statsLoading, setStatsLoading] = useState(false);
  const [statsError, setStatsError] = useState<string | null>(null);

  const loadData = useCallback(async () => {
    setLoading(true);
    setError(null);
    try {
      const [loadedUsers, loadedListings, loadedReports, loadedForbiddenWords] = await Promise.all([
        apiFetch<AuthUser[]>('/admin/users'),
        apiFetch<AdminListing[]>('/admin/listings'),
        apiFetch<AdminListingReport[]>('/admin/reports'),
        apiFetch<ForbiddenWord[]>('/admin/forbidden-words'),
      ]);
      setUsers(loadedUsers);
      setListings(loadedListings);
      setReports(loadedReports);
      setForbiddenWords(loadedForbiddenWords);
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Nie udało się pobrać danych panelu.');
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    loadData();
  }, [loadData]);

  const runAction = async (actionKey: string, action: () => Promise<unknown>) => {
    setBusyId(actionKey);
    setError(null);
    try {
      await action();
      await loadData();
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Operacja nie powiodła się.');
    } finally {
      setBusyId(null);
    }
  };

  const openUserPreview = useCallback(async (id: number) => {
    try {
      const freshUsers = await apiFetch<AuthUser[]>('/admin/users');
      setUsers(freshUsers);
      setVerificationUser(freshUsers.find((item) => item.id === id) ?? null);
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Nie udało się pobrać danych użytkownika.');
    }
  }, []);

  const loadMessageThreads = useCallback(async (silent = false) => {
    const regularUsers = users.filter((item) => item.role !== 'ADMIN');
    if (regularUsers.length === 0) {
      setMessageThreads([]);
      setMessageThreadsError(null);
      setMessageThreadsLoading(false);
      return;
    }

    if (!silent) {
      setMessageThreadsLoading(true);
    }
    setMessageThreadsError(null);

    try {
      const conversations = await Promise.allSettled(
        regularUsers.map((item) => apiFetch<AdminMessage[]>(`/admin/users/${item.id}/messages`)),
      );

      const mappedThreads: AdminMessageThread[] = [];

      conversations.forEach((result, index) => {
        if (result.status !== 'fulfilled') {
          return;
        }

        const conversation = result.value;
        if (conversation.length === 0) {
          return;
        }

        const lastMessage = conversation[conversation.length - 1];
        let awaitingAdminReply = 0;
        for (let cursor = conversation.length - 1; cursor >= 0; cursor -= 1) {
          if (conversation[cursor].mine) {
            break;
          }
          awaitingAdminReply += 1;
        }

        mappedThreads.push({
          user: regularUsers[index],
          lastMessage,
          totalMessages: conversation.length,
          awaitingAdminReply,
        });
      });

      mappedThreads.sort((left, right) => new Date(right.lastMessage.createdAt).getTime() - new Date(left.lastMessage.createdAt).getTime());
      setMessageThreads(mappedThreads);

      if (conversations.every((entry) => entry.status === 'rejected')) {
        setMessageThreadsError('Nie udało się pobrać wiadomości użytkowników.');
      }
    } catch (err) {
      setMessageThreadsError(err instanceof Error ? err.message : 'Nie udało się pobrać wiadomości użytkowników.');
    } finally {
      if (!silent) {
        setMessageThreadsLoading(false);
      }
    }
  }, [users]);

  useEffect(() => {
    if (tab !== 'messages' || loading) {
      return;
    }

    loadMessageThreads();
    const intervalId = window.setInterval(() => {
      void loadMessageThreads(true);
    }, 5000);

    return () => window.clearInterval(intervalId);
  }, [tab, loading, loadMessageThreads]);

  const loadStats = useCallback(async (silent = false) => {
    if (!silent) {
      setStatsLoading(true);
    }
    setStatsError(null);
    try {
      const query = new URLSearchParams();
      if (statsPeriodMode === 'custom') {
        if (statsCustomFrom) {
          query.set('from', statsCustomFrom);
        }
        if (statsCustomTo) {
          query.set('to', statsCustomTo);
        }
        if (!statsCustomFrom && !statsCustomTo) {
          query.set('days', String(statsPeriodDays));
        }
      } else {
        query.set('days', String(statsPeriodDays));
      }

      const data = await apiFetch<AdminStatsResponse>(`/admin/stats?${query.toString()}`);
      setAdminStats(data);
    } catch (err) {
      setStatsError(err instanceof Error ? err.message : 'Nie udało się pobrać statystyk.');
    } finally {
      if (!silent) {
        setStatsLoading(false);
      }
    }
  }, [statsPeriodDays, statsPeriodMode, statsCustomFrom, statsCustomTo]);

  useEffect(() => {
    if (tab !== 'stats' || loading) {
      return;
    }

    loadStats();
    const intervalId = window.setInterval(() => {
      void loadStats(true);
    }, 10000);

    return () => window.clearInterval(intervalId);
  }, [tab, loading, loadStats]);

  const pendingCount = listings.filter((item) => item.status === 'PENDING').length;
  const pendingVerificationCount = users.filter((item) => item.role !== 'ADMIN' && !item.verified).length;
  const pendingReportsCount = reports.filter((item) => item.status === 'OPEN').length;

  const isImageReportAttachment = (attachment: AdminListingReportAttachment) => {
    if (attachment.fileType && attachment.fileType.toLowerCase().startsWith('image/')) {
      return true;
    }
    return /^data:image\//.test(attachment.dataUrl ?? '');
  };

  const recentListings = useMemo(
    () => [...listings]
      .sort((left, right) => new Date(right.createdAt).getTime() - new Date(left.createdAt).getTime())
      .slice(0, 5),
    [listings],
  );

  const recentUsers = useMemo(
    () => [...users]
      .sort((left, right) => new Date(right.createdAt).getTime() - new Date(left.createdAt).getTime())
      .slice(0, 5),
    [users],
  );

  const activeMeta = ADMIN_TAB_META[tab];
  const formattedGeneratedAt = adminStats
    ? new Date(adminStats.generatedAt).toLocaleString('pl-PL', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' })
    : null;
  const applyQuickStatsRange = useCallback((range: AdminStatsQuickRange) => {
    if (!range) {
      return;
    }
    const today = new Date();
    const to = new Date(today);
    const from = new Date(today);
    if (range === 'last7') {
      from.setDate(from.getDate() - 6);
    } else if (range === 'last14') {
      from.setDate(from.getDate() - 13);
    } else if (range === 'last30') {
      from.setDate(from.getDate() - 29);
    }

    setStatsPeriodMode('custom');
    setStatsCustomFrom(toDateInputValue(from));
    setStatsCustomTo(toDateInputValue(to));
    setStatsQuickRange(range);
  }, []);
  const statsHeadingLabel = useMemo(() => {
    if (!adminStats) {
      return statsPeriodMode === 'custom' ? 'Wybrany okres' : (statsPeriodDays === 1 ? 'Dziś' : `Ostatnie ${statsPeriodDays} dni`);
    }
    return statsPeriodMode === 'custom'
      ? 'Wybrany okres'
      : (adminStats.periodDays === 1 ? 'Dziś' : `Ostatnie ${adminStats.periodDays} dni`);
  }, [adminStats, statsPeriodDays, statsPeriodMode]);

  const handleAddForbiddenWord = async () => {
    const word = newForbiddenWord.trim();
    if (!word || savingForbiddenWord) {
      return;
    }

    setSavingForbiddenWord(true);
    setError(null);
    try {
      const updatedWords = await apiFetch<ForbiddenWord[]>('/admin/forbidden-words', {
        method: 'POST',
        body: JSON.stringify({ word }),
      });
      setForbiddenWords(updatedWords);
      setNewForbiddenWord('');
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Nie udało się dodać słowa.');
    } finally {
      setSavingForbiddenWord(false);
    }
  };

  const handleDeleteForbiddenWord = async (item: ForbiddenWord) => {
    if (deletingWordId !== null) {
      return;
    }
    setDeletingWordId(item.id);
    setError(null);
    try {
      await apiFetch(`/admin/forbidden-words/${item.id}`, { method: 'DELETE' });
      setForbiddenWords((current) => current.filter((word) => word.id !== item.id));
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Nie udało się usunąć słowa.');
    } finally {
      setDeletingWordId(null);
    }
  };

  const foldText = (value: string) =>
    value.toLowerCase().normalize('NFD').replace(/\p{Diacritic}/gu, '');
  const normalizedForbiddenSearch = foldText(forbiddenSearch.trim());
  const filteredForbiddenWords = normalizedForbiddenSearch
    ? forbiddenWords.filter((item) => foldText(item.word).includes(normalizedForbiddenSearch))
    : forbiddenWords;

  const normalizedUserSearch = foldText(userSearch.trim());
  const filteredUsers = normalizedUserSearch
    ? users.filter((item) => {
      const normalizedEmail = foldText(item.email);
      const normalizedName = foldText(item.fullName || '');
      return normalizedEmail.includes(normalizedUserSearch) || normalizedName.includes(normalizedUserSearch);
    })
    : users;

  const handleGrantAdmin = async (item: AuthUser) => {
    const shouldGrantAdmin = window.confirm(`Czy na pewno chcesz nadać rolę administratora użytkownikowi ${item.fullName || item.email}?`);
    if (!shouldGrantAdmin) {
      return;
    }
    await runAction(`grant-admin-${item.id}`, () => apiFetch(`/admin/users/${item.id}/grant-admin`, { method: 'POST' }));
  };

  const handleRevokeAdmin = async (item: AuthUser) => {
    const shouldRevokeAdmin = window.confirm(`Czy na pewno chcesz odebrać rolę administratora użytkownikowi ${item.fullName || item.email}?`);
    if (!shouldRevokeAdmin) {
      return;
    }
    await runAction(`revoke-admin-${item.id}`, () => apiFetch(`/admin/users/${item.id}/revoke-admin`, { method: 'POST' }));
  };

  const handleResetPassword = async (item: AuthUser) => {
    const shouldResetPassword = window.confirm(`Czy zresetować hasło użytkownika ${item.fullName || item.email}?`);
    if (!shouldResetPassword) {
      return;
    }

    setBusyId(`reset-password-${item.id}`);
    setError(null);
    try {
      const response = await apiFetch<AdminResetPasswordResponse>(`/admin/users/${item.id}/reset-password`, { method: 'POST' });
      window.alert(`Nowe hasło tymczasowe dla ${item.email}: ${response.temporaryPassword}`);
      await loadData();
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Nie udało się zresetować hasła.');
    } finally {
      setBusyId(null);
    }
  };

  if (isUnknownAdminSection) {
    return <NotFoundPage />;
  }

  return (
    <section className="admin-page admin-page-shell" aria-label="Panel administratora">
      <div className="admin-shell admin-shell-with-sidebar">
        <aside className="admin-sidebar">
          <span className="admin-badge admin-sidebar-badge"><Icon name="shield" /> Administrator</span>

          <nav className="admin-nav">
            {ADMIN_NAV_ITEMS.map((item) => (
              <NavLink
                key={item.id}
                to={adminTabPath(item.id)}
                end={item.id === 'dashboard'}
                className={navLinkClass}
              >
                <Icon name={item.icon} /> {item.label}
                {item.id === 'listings' && pendingCount > 0 && <b>{pendingCount}</b>}
                {item.id === 'users' && pendingVerificationCount > 0 && <b>{pendingVerificationCount}</b>}
                {item.id === 'reports' && pendingReportsCount > 0 && <b>{pendingReportsCount}</b>}
              </NavLink>
            ))}
          </nav>

          <p className="admin-nav-group-label">Narzędzia</p>
          <nav className="admin-nav">
            {ADMIN_TOOL_ITEMS.map((item) => (
              <NavLink key={item.id} to={adminTabPath(item.id)} className={navLinkClass}>
                <Icon name={item.icon} /> {item.label}
              </NavLink>
            ))}
          </nav>

          <div className="admin-sidebar-status">
            <strong><Icon name="shield" /> System działa poprawnie</strong>
            <p>Wszystkie usługi działają bez zarzutu.</p>
            <button type="button" onClick={loadData}>Odśwież status <Icon name="arrow" /></button>
          </div>

          <Link className="admin-back" to={ROUTES.home}>
            <Icon name="arrow" /> Wróć do strony głównej
          </Link>
        </aside>

        <div className="admin-main">
          <header className="admin-head">
            <div>
              <h1>{activeMeta.title}</h1>
              <p>Zalogowano jako <strong>{user?.fullName || user?.email}</strong>. {activeMeta.subtitle}</p>
            </div>
            <button type="button" className="admin-refresh" onClick={loadData} disabled={loading}>
              <Icon name="shuffle" /> Odśwież
            </button>
          </header>

          {error && <p className="admin-error" role="alert">{error}</p>}
          {loading && <p className="admin-empty">Ładowanie danych…</p>}

          {!loading && tab === 'dashboard' && (
            <div className="admin-dashboard">
              <div className="admin-stats-grid">
                <article className="admin-stat-card">
                  <span className="admin-stat-icon tone-green"><Icon name="document" /></span>
                  <div>
                    <strong>{listings.length.toLocaleString('pl-PL')}</strong>
                    <small>Łączna liczba ogłoszeń</small>
                  </div>
                </article>
                <article className="admin-stat-card">
                  <span className="admin-stat-icon tone-blue"><Icon name="user" /></span>
                  <div>
                    <strong>{users.length.toLocaleString('pl-PL')}</strong>
                    <small>Zarejestrowani użytkownicy</small>
                  </div>
                </article>
                <article className="admin-stat-card">
                  <span className="admin-stat-icon tone-yellow"><Icon name="eye" /></span>
                  <div>
                    <strong>{pendingCount.toLocaleString('pl-PL')}</strong>
                    <small>Ogłoszenia oczekujące</small>
                  </div>
                </article>
                <article className="admin-stat-card">
                  <span className="admin-stat-icon tone-purple"><Icon name="message" /></span>
                  <div>
                    <strong>—</strong>
                    <small>Wiadomości (wkrótce)</small>
                  </div>
                </article>
                <article className="admin-stat-card">
                  <span className="admin-stat-icon tone-pink"><Icon name="warning" /></span>
                  <div>
                    <strong>{pendingReportsCount.toLocaleString('pl-PL')}</strong>
                    <small>Otwarte zgłoszenia</small>
                  </div>
                </article>
              </div>

              <div className="admin-dashboard-grid">
                <section className="admin-card admin-recent-listings">
                  <div className="admin-card-head">
                    <h2>Ostatnie ogłoszenia</h2>
                    <Link to={adminTabPath('listings')}>Zobacz wszystkie <Icon name="arrow" /></Link>
                  </div>
                  {recentListings.length === 0 ? (
                    <p className="admin-empty">Brak ogłoszeń do wyświetlenia.</p>
                  ) : (
                    <div className="admin-table admin-table-compact" role="table">
                      <div className="admin-row admin-row-head" role="row">
                        <span>Ogłoszenie</span>
                        <span>Cena</span>
                        <span>Autor</span>
                        <span>Status</span>
                      </div>
                      {recentListings.map((item) => (
                        <div className="admin-row" role="row" key={item.id}>
                          <span className="admin-cell-main">
                            <strong>{item.title}</strong>
                            <small>{item.city} • {formatAdminDate(item.createdAt)}</small>
                          </span>
                          <span>{item.price.toLocaleString('pl-PL')} zł</span>
                          <span className="admin-cell-owner">{item.ownerEmail || '—'}</span>
                          <span><em className={`admin-status ${item.status.toLowerCase()}`}>{ADMIN_STATUS_LABELS[item.status]}</em></span>
                        </div>
                      ))}
                    </div>
                  )}
                </section>

                <section className="admin-card admin-recent-users">
                  <div className="admin-card-head">
                    <h2>Aktywność użytkowników</h2>
                    <Link to={adminTabPath('users')}>Zobacz wszystkich <Icon name="arrow" /></Link>
                  </div>
                  {recentUsers.length === 0 ? (
                    <p className="admin-empty">Brak użytkowników do wyświetlenia.</p>
                  ) : (
                    <ul className="admin-activity-list">
                      {recentUsers.map((item) => (
                        <li key={item.id}>
                          <span className="admin-activity-avatar">{(item.fullName || item.email).slice(0, 1).toUpperCase()}</span>
                          <div>
                            <strong>{item.fullName || item.email}</strong>
                            <small>Dołączył(a) do serwisu</small>
                          </div>
                          <em>{formatAdminDate(item.createdAt)}</em>
                        </li>
                      ))}
                    </ul>
                  )}
                </section>
              </div>

              <section className="admin-card admin-quick-actions">
                <div className="admin-card-head">
                  <h2>Szybkie akcje</h2>
                </div>
                <div className="admin-quick-actions-grid">
                  <Link to={ROUTES.add}><Icon name="document" /> Dodaj nowe ogłoszenie <Icon name="arrow" /></Link>
                  <Link to={adminTabPath('categories')}><Icon name="list" /> Zarządzaj kategoriami <Icon name="arrow" /></Link>
                  <Link to={adminTabPath('locations')}><Icon name="pin" /> Zarządzaj lokalizacjami <Icon name="arrow" /></Link>
                  <Link to={adminTabPath('promotions')}><Icon name="spark" /> Zarządzaj banerami <Icon name="arrow" /></Link>
                </div>
              </section>
            </div>
          )}

          {!loading && tab === 'reports' && (
            <div className="admin-card">
              {reports.length === 0 ? (
                <p className="admin-empty">Brak zgłoszeń.</p>
              ) : (
                <div className="admin-table admin-table-reports" role="table">
                  <div className="admin-row admin-row-head admin-row-report" role="row">
                    <span>Zgłoszenie</span>
                    <span>Ogłoszenie</span>
                    <span>Zgłaszający</span>
                    <span>Status</span>
                    <span>Zostaw ogłoszenie</span>
                    <span>Usuń ogłoszenie</span>
                    <span>Akcje</span>
                  </div>
                  {reports.map((item) => (
                    <div className="admin-row admin-row-report" role="row" key={item.id}>
                      <span className="admin-cell-main">
                        <strong>{item.reasonTitle}</strong>
                        <small>
                          #{item.id} • {formatAdminDate(item.createdAt)}
                          {item.details ? ` • ${item.details}` : ''}
                          {item.attachmentNames.length > 0 ? ` • Załączniki: ${item.attachmentNames.join(', ')}` : ''}
                        </small>
                      </span>
                      <span className="admin-cell-main">
                        <strong>{item.listingTitle}</strong>
                        <small>
                          {item.listingCity} • ID: {item.listingId}
                          {item.listingOwnerEmail ? ` • ${item.listingOwnerEmail}` : ''}
                          {item.listingDeleted ? ' • Usunięte' : ''}
                        </small>
                      </span>
                      <span className="admin-cell-main">
                        <strong>{item.reporterName || item.reporterEmail}</strong>
                        <small>{item.reporterEmail}</small>
                      </span>
                      <span>
                        <em className={`admin-status ${item.status === 'OPEN' ? 'pending' : 'approved'}`}>
                          {ADMIN_REPORT_STATUS_LABELS[item.status]}
                        </em>
                        {item.resolvedAt && (
                          <small style={{ display: 'block', marginTop: 4 }}>
                            {formatAdminDate(item.resolvedAt)}{item.resolvedByEmail ? ` • ${item.resolvedByEmail}` : ''}
                            {item.resolutionNote ? ` • ${item.resolutionNote}` : ''}
                          </small>
                        )}
                      </span>
                      <span>
                        {item.status !== 'RESOLVED' ? (
                          <button
                            type="button"
                            className="admin-btn approve"
                            disabled={busyId === `report-${item.id}`}
                            onClick={() => runAction(`report-${item.id}`, () => apiFetch(`/admin/reports/${item.id}/resolve`, { method: 'POST', body: JSON.stringify({ note: 'Po weryfikacji: ogłoszenie zostaje.' }) }))}
                          >
                            Zostaw ogłoszenie
                          </button>
                        ) : '—'}
                      </span>
                      <span>
                        {!item.listingDeleted && (
                          <button
                            type="button"
                            className="admin-btn danger"
                            disabled={busyId === `report-${item.id}`}
                            onClick={() => {
                              const shouldDelete = window.confirm('Czy na pewno chcesz usunąć to ogłoszenie? Tej operacji nie można cofnąć.');
                              if (!shouldDelete) {
                                return;
                              }
                              runAction(`report-${item.id}`, () => apiFetch(`/admin/reports/${item.id}/delete-listing`, { method: 'POST' }));
                            }}
                          >
                            Usuń ogłoszenie
                          </button>
                        )}
                        {item.listingDeleted && '—'}
                      </span>
                      <span className="admin-actions">
                        <button
                          type="button"
                          className="admin-btn view"
                          onClick={() => setDetailsReport(item)}
                        >
                          Szczegóły zgłoszenia
                        </button>
                        {!item.listingDeleted && (
                          <button
                            type="button"
                            className="admin-btn view"
                            onClick={() => setPreviewListingId(item.listingId)}
                          >
                            Podgląd ogłoszenia
                          </button>
                        )}
                      </span>
                    </div>
                  ))}
                </div>
              )}
            </div>
          )}

          {!loading && tab === 'listings' && (
            <div className="admin-card">
              {listings.length === 0 ? (
                <p className="admin-empty">Brak ogłoszeń do moderacji.</p>
              ) : (
                <div className="admin-table" role="table">
                  <div className="admin-row admin-row-head" role="row">
                    <span>Ogłoszenie</span>
                    <span>Typ</span>
                    <span>Cena</span>
                    <span>Autor</span>
                    <span>Status</span>
                    <span>Akcje</span>
                  </div>
                  {listings.map((item) => (
                    <div className="admin-row" role="row" key={item.id}>
                      <span className="admin-cell-main">
                        <strong>{item.title}</strong>
                        <small>{item.city} • {item.area} m² • {item.rooms} pok. • {formatAdminDate(item.createdAt)}</small>
                      </span>
                      <span>{ADMIN_OFFER_LABELS[item.offerType]} / {ADMIN_PROPERTY_LABELS[item.propertyType]}</span>
                      <span>{item.price.toLocaleString('pl-PL')} zł</span>
                      <span className="admin-cell-owner">{item.ownerEmail || '—'}</span>
                      <span>
                        <em className={`admin-status ${item.status.toLowerCase()}`}>{ADMIN_STATUS_LABELS[item.status]}</em>
                      </span>
                      <span className="admin-actions">
                        <button type="button" className="admin-btn view" onClick={() => setPreviewListingId(item.id)}>
                          Zobacz
                        </button>
                        {item.status !== 'APPROVED' && (
                          <button type="button" className="admin-btn approve" disabled={busyId === `listing-${item.id}`}
                            onClick={() => runAction(`listing-${item.id}`, () => apiFetch(`/admin/listings/${item.id}/approve`, { method: 'POST' }))}>
                            Zatwierdź
                          </button>
                        )}
                        {item.status !== 'REJECTED' && (
                          <button type="button" className="admin-btn reject" disabled={busyId === `listing-${item.id}`}
                            onClick={() => runAction(`listing-${item.id}`, () => apiFetch(`/admin/listings/${item.id}/reject`, { method: 'POST' }))}>
                            Odrzuć
                          </button>
                        )}
                        <button type="button" className="admin-btn danger" disabled={busyId === `listing-${item.id}`}
                          onClick={() => runAction(`listing-${item.id}`, () => apiFetch(`/admin/listings/${item.id}`, { method: 'DELETE' }))}>
                          Usuń
                        </button>
                      </span>
                    </div>
                  ))}
                </div>
              )}
            </div>
          )}

          {!loading && tab === 'users' && (
            <div className="admin-card">
              {users.length === 0 ? (
                <p className="admin-empty">Brak zarejestrowanych użytkowników.</p>
              ) : filteredUsers.length === 0 ? (
                <>
                  <div className="admin-users-search-bar">
                    <label className="admin-users-search" aria-label="Wyszukiwarka użytkowników">
                      <Icon name="search" />
                      <input
                        type="text"
                        value={userSearch}
                        onChange={(event) => setUserSearch(event.target.value)}
                        placeholder="Szukaj po e-mailu lub nazwie użytkownika"
                      />
                      {userSearch.trim().length > 0 && (
                        <button
                          type="button"
                          className="admin-users-search-clear"
                          onClick={() => setUserSearch('')}
                          aria-label="Wyczyść wyszukiwanie"
                        >
                          ×
                        </button>
                      )}
                    </label>
                    <small className="admin-users-search-count">Wyniki: 0 z {users.length}</small>
                  </div>
                  <p className="admin-empty">Brak użytkowników pasujących do wyszukania.</p>
                </>
              ) : (
                <>
                  <div className="admin-users-search-bar">
                    <label className="admin-users-search" aria-label="Wyszukiwarka użytkowników">
                      <Icon name="search" />
                      <input
                        type="text"
                        value={userSearch}
                        onChange={(event) => setUserSearch(event.target.value)}
                        placeholder="Szukaj po e-mailu lub nazwie użytkownika"
                      />
                      {userSearch.trim().length > 0 && (
                        <button
                          type="button"
                          className="admin-users-search-clear"
                          onClick={() => setUserSearch('')}
                          aria-label="Wyczyść wyszukiwanie"
                        >
                          ×
                        </button>
                      )}
                    </label>
                    <small className="admin-users-search-count">Wyniki: {filteredUsers.length} z {users.length}</small>
                  </div>

                  <div className="admin-table admin-table-users" role="table">
                    <div className="admin-row admin-row-head" role="row">
                      <span>Użytkownik</span>
                      <span>Logowanie</span>
                      <span>Typ konta</span>
                      <span>Rola</span>
                      <span>Weryfikacja</span>
                      <span>Dane</span>
                      <span>Status</span>
                      <span>Akcje</span>
                      <span>Konto</span>
                    </div>
                    {filteredUsers.map((item) => (
                      <div className="admin-row" role="row" key={item.id}>
                        <span className="admin-cell-main">
                          <strong title={item.fullName || '—'}>{item.fullName || '—'}</strong>
                          <small title={item.email}>{item.email}</small>
                        </span>
                        <span>{item.provider === 'LOCAL' ? 'E-mail' : item.provider === 'GOOGLE' ? 'Google' : 'Facebook'}</span>
                        <span><em className={`admin-status ${item.accountType === 'COMPANY' ? 'company' : 'approved'}`}>{item.accountType === 'COMPANY' ? 'Firma' : 'Osoba prywatna'}</em></span>
                        <span><em className={`admin-status ${item.role === 'ADMIN' ? 'approved' : 'pending'}`}>{item.role === 'ADMIN' ? 'Administrator' : 'Użytkownik'}</em></span>
                        <span className="admin-cell-verification">
                          {item.role === 'ADMIN' || item.verified ? (
                            <em className="admin-status approved">Zweryfikowany</em>
                          ) : (
                            <em className="admin-status pending">Oczekuje</em>
                          )}
                        </span>
                        <span className="admin-cell-preview">
                          {item.role !== 'ADMIN' && (
                            item.verified ? (
                              <button type="button" className="admin-btn" onClick={() => openUserPreview(item.id)}>
                                Podgląd danych
                              </button>
                            ) : (
                              <button type="button" className="admin-btn" onClick={() => openUserPreview(item.id)}>
                                Sprawdź dane
                              </button>
                            )
                          )}
                        </span>
                        <span>{item.blocked ? <em className="admin-status rejected">Zablokowany</em> : <em className="admin-status approved">Aktywny</em>}</span>
                        <span className="admin-actions">
                          {item.role === 'ADMIN' ? (
                            user?.id === item.id ? (
                              <span className="admin-muted">Twoje konto administratora</span>
                            ) : (
                              <button
                                type="button"
                                className="admin-btn reject"
                                disabled={busyId === `revoke-admin-${item.id}`}
                                onClick={() => handleRevokeAdmin(item)}
                              >
                                Odbierz admina
                              </button>
                            )
                          ) : (
                            <button type="button" className="admin-btn" onClick={() => setMessageUser(item)}>
                              Wiadomość
                            </button>
                          )}
                        </span>
                        <span className="admin-account-actions-cell">
                          {item.role === 'ADMIN' ? (
                            <span className="admin-muted">—</span>
                          ) : (
                            <div className="admin-account-actions-table" role="group" aria-label={`Akcje konta użytkownika ${item.fullName || item.email}`}>
                              <button
                                type="button"
                                className="admin-btn admin-btn-compact"
                                disabled={busyId === `reset-password-${item.id}`}
                                onClick={() => handleResetPassword(item)}
                              >
                                Reset hasła
                              </button>
                              <button
                                type="button"
                                className="admin-btn admin-btn-compact"
                                disabled={busyId === `grant-admin-${item.id}`}
                                onClick={() => handleGrantAdmin(item)}
                              >
                                Nadaj admina
                              </button>
                              <button
                                type="button"
                                className="admin-btn danger admin-btn-compact"
                                disabled={busyId === `user-${item.id}`}
                                onClick={() => {
                                  const shouldDelete = window.confirm('Czy na pewno chcesz usunąć konto tego użytkownika? Operacji nie da się cofnąć.');
                                  if (!shouldDelete) {
                                    return;
                                  }
                                  runAction(`user-${item.id}`, () => apiFetch(`/admin/users/${item.id}`, { method: 'DELETE' }));
                                }}
                              >
                                Usuń konto
                              </button>
                            </div>
                          )}
                        </span>
                      </div>
                    ))}
                  </div>
                </>
              )}
            </div>
          )}

          {verificationUser && (
            <div className="listing-delete-overlay" role="dialog" aria-modal="true" aria-label="Dane użytkownika">
              <div className="admin-verify-modal">
                <button type="button" className="listing-delete-close" aria-label="Zamknij" onClick={() => setVerificationUser(null)}>×</button>
                <div className="admin-verify-icon"><Icon name="shield" /></div>
                <h2>{verificationUser.verified ? 'Dane użytkownika' : 'Weryfikacja konta'}</h2>
                <p className="admin-verify-subtitle">
                  {verificationUser.verified
                    ? 'Podgląd aktualnych danych konta.'
                    : 'Sprawdź dane podane przez użytkownika przed zatwierdzeniem konta.'}
                </p>

                <div className="admin-verify-details">
                  {verificationUser.accountType === 'COMPANY' ? (
                    <>
                      <article><span>Nazwa firmy</span><strong>{verificationUser.fullName || '—'}</strong></article>
                      <article><span>NIP</span><strong>{verificationUser.nip || 'Nie podano'}</strong></article>
                      <article><span>Adres e-mail</span><strong>{verificationUser.email}</strong></article>
                      <article><span>Numer telefonu</span><strong>{verificationUser.phone || 'Nie podano'}</strong></article>
                    </>
                  ) : (
                    <>
                      <article><span>Imię i nazwisko</span><strong>{verificationUser.fullName || '—'}</strong></article>
                      <article><span>Adres e-mail</span><strong>{verificationUser.email}</strong></article>
                      <article><span>Numer telefonu</span><strong>{verificationUser.phone || 'Nie podano'}</strong></article>
                      <article><span>Data urodzenia</span><strong>{verificationUser.birthDate ? verificationUser.birthDate.split('-').reverse().join('.') : 'Nie podano'}</strong></article>
                    </>
                  )}
                </div>

                {verificationUser.verified ? (
                  <div className="listing-delete-actions">
                    <button type="button" className="admin-btn" onClick={() => setVerificationUser(null)}>
                      Zamknij
                    </button>
                  </div>
                ) : (
                  <>
                    <p className="admin-verify-reject-note">Odrzucenie usunie konto i trwale zablokuje ten adres e-mail przed ponowną rejestracją.</p>

                    <div className="listing-delete-actions">
                      <button
                        type="button"
                        className="admin-btn danger"
                        disabled={busyId === `reject-${verificationUser.id}`}
                        onClick={async () => {
                          await runAction(`reject-${verificationUser.id}`, () => apiFetch(`/admin/users/${verificationUser.id}/reject`, { method: 'POST' }));
                          setVerificationUser(null);
                        }}
                      >
                        Odrzuć i zablokuj e-mail
                      </button>
                      <button
                        type="button"
                        className="admin-btn approve"
                        disabled={busyId === `verify-${verificationUser.id}`}
                        onClick={async () => {
                          await runAction(`verify-${verificationUser.id}`, () => apiFetch(`/admin/users/${verificationUser.id}/verify`, { method: 'POST' }));
                          setVerificationUser(null);
                        }}
                      >
                        Zaakceptuj konto
                      </button>
                    </div>
                  </>
                )}
              </div>
            </div>
          )}

          {messageUser && (
            <AdminMessageModal user={messageUser} onClose={() => setMessageUser(null)} />
          )}

          {previewListingId != null && (
            <AdminListingPreviewModal
              listingId={previewListingId}
              busy={busyId === `listing-${previewListingId}`}
              onClose={() => setPreviewListingId(null)}
              onApprove={async () => {
                await runAction(`listing-${previewListingId}`, () => apiFetch(`/admin/listings/${previewListingId}/approve`, { method: 'POST' }));
                setPreviewListingId(null);
              }}
              onReject={async () => {
                await runAction(`listing-${previewListingId}`, () => apiFetch(`/admin/listings/${previewListingId}/reject`, { method: 'POST' }));
                setPreviewListingId(null);
              }}
            />
          )}

          {detailsReport && (
            <AdminReportDetailsModal
              report={detailsReport}
              onClose={() => setDetailsReport(null)}
              onPreviewListing={detailsReport.listingDeleted ? null : () => setPreviewListingId(detailsReport.listingId)}
              isImageAttachment={isImageReportAttachment}
            />
          )}

          {!loading && tab === 'messages' && (
            <div className="admin-card admin-message-inbox">
              <div className="admin-card-head">
                <h2>Wiadomości z formularza „Napisz do nas”</h2>
                <button type="button" onClick={() => loadMessageThreads()} disabled={messageThreadsLoading}>
                  Odśwież <Icon name="arrow" />
                </button>
              </div>

              {messageThreadsError && <p className="admin-error" role="alert">{messageThreadsError}</p>}

              {messageThreadsLoading ? (
                <p className="admin-empty">Wczytywanie wiadomości...</p>
              ) : messageThreads.length === 0 ? (
                <p className="admin-empty">Brak wiadomości od użytkowników. Gdy ktoś wyśle formularz „Napisz do nas”, pojawi się tutaj.</p>
              ) : (
                <div className="admin-table admin-table-message-inbox" role="table">
                  <div className="admin-row admin-row-head" role="row">
                    <span>Użytkownik</span>
                    <span>Ostatnia wiadomość</span>
                    <span>Aktywność</span>
                    <span>Akcje</span>
                  </div>
                  {messageThreads.map((thread) => (
                    <div className="admin-row" role="row" key={thread.user.id}>
                      <span className="admin-cell-main">
                        <strong>{thread.user.fullName || thread.user.email}</strong>
                        <small>{thread.user.email}</small>
                      </span>
                      <span className="admin-cell-main">
                        <strong>{thread.lastMessage.mine ? 'Administrator' : 'Użytkownik'}</strong>
                        <small>{thread.lastMessage.content}</small>
                      </span>
                      <span className="admin-cell-main">
                        <strong>{new Date(thread.lastMessage.createdAt).toLocaleString('pl-PL', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' })}</strong>
                        <small>{thread.awaitingAdminReply > 0 ? `Nowe od użytkownika: ${thread.awaitingAdminReply}` : `Wiadomości: ${thread.totalMessages}`}</small>
                      </span>
                      <span className="admin-actions">
                        <button type="button" className="admin-btn" onClick={() => setMessageUser(thread.user)}>
                          Otwórz rozmowę
                        </button>
                      </span>
                    </div>
                  ))}
                </div>
              )}
            </div>
          )}
          {!loading && tab === 'payments' && <AdminPlaceholderCard icon="credit-card" label="Płatności" />}
          {!loading && tab === 'stats' && (
            <div className="admin-stats-live">
              <div className="admin-stats-top-grid">
                <article className="admin-stats-top-card">
                  <span><Icon name="document" /></span>
                  <div>
                    <strong>{adminStats?.summary.activeListings ?? 0}</strong>
                    <small>Aktywne ogłoszenia</small>
                  </div>
                </article>
                <article className="admin-stats-top-card">
                  <span><Icon name="eye" /></span>
                  <div>
                    <strong>{adminStats?.summary.pendingListings ?? 0}</strong>
                    <small>Oczekujące ogłoszenia</small>
                  </div>
                </article>
              </div>

              <div className="admin-stats-layout">
                <aside className="admin-stats-sidebar">
                  <h3>Okres</h3>
                  <div className="admin-stats-period-buttons">
                    {ADMIN_STATS_PERIOD_OPTIONS.map((option) => (
                      <button
                        key={option.days}
                        type="button"
                        className={statsPeriodMode === 'preset' && statsPeriodDays === option.days ? 'active' : ''}
                        onClick={() => {
                          setStatsPeriodMode('preset');
                          setStatsPeriodDays(option.days);
                          setStatsQuickRange('');
                        }}
                      >
                        <Icon name="calendar" /> {option.label}
                      </button>
                    ))}
                    <button
                      type="button"
                      className={statsPeriodMode === 'custom' ? 'active' : ''}
                      onClick={() => {
                        setStatsPeriodMode('custom');
                        setStatsQuickRange('');
                      }}
                    >
                      <Icon name="calendar" /> Dowolny okres
                    </button>
                  </div>

                  {statsPeriodMode === 'custom' && (
                    <div className="admin-stats-custom-range">
                      <label>
                        <span>Szybki wybór</span>
                        <select
                          value={statsQuickRange}
                          onChange={(event) => applyQuickStatsRange(event.target.value as AdminStatsQuickRange)}
                        >
                          <option value="">Wybierz gotowy zakres</option>
                          <option value="today">Dzisiaj</option>
                          <option value="last7">Ostatnie 7 dni</option>
                          <option value="last14">Ostatnie 14 dni</option>
                          <option value="last30">Ostatnie 30 dni</option>
                        </select>
                      </label>
                      <label>
                        <span>Od</span>
                        <input
                          type="date"
                          value={statsCustomFrom}
                          onChange={(event) => {
                            setStatsCustomFrom(event.target.value);
                            setStatsQuickRange('');
                          }}
                          max={statsCustomTo || undefined}
                        />
                      </label>
                      <label>
                        <span>Do</span>
                        <input
                          type="date"
                          value={statsCustomTo}
                          onChange={(event) => {
                            setStatsCustomTo(event.target.value);
                            setStatsQuickRange('');
                          }}
                          min={statsCustomFrom || undefined}
                        />
                      </label>
                    </div>
                  )}
                  <div className="admin-stats-side-summary">
                    <strong>Czeka do akceptacji: {adminStats?.summary.pendingVerificationUsers ?? 0}</strong>
                    <p>Moderacja treści: {adminStats?.summary.openReports ?? 0}</p>
                    <p>Wiadomości do admina: {adminStats?.summary.messagesToAdminTotal ?? 0}</p>
                    <p>Zablokowani użytkownicy: {adminStats?.summary.blockedUsers ?? 0}</p>
                  </div>
                </aside>

                <div className="admin-stats-main">
                  <div className="admin-stats-main-head">
                    <h2>{statsHeadingLabel}</h2>
                  </div>

                  {statsError && <p className="admin-error" role="alert">{statsError}</p>}
                  {statsLoading && <p className="admin-empty">Wczytywanie statystyk...</p>}

                  {adminStats && (
                    <>
                      <div className="admin-stats-cards-grid">
                        <article className="admin-stats-card"><h4><Icon name="message" /> Wiadomości do admina</h4><strong>{adminStats.period.messagesToAdmin}</strong></article>
                        <article className="admin-stats-card"><h4><Icon name="document" /> Dodane ogłoszenia</h4><strong>{adminStats.period.newListings}</strong></article>
                        <article className="admin-stats-card"><h4><Icon name="warning" /> Nowe zgłoszenia</h4><strong>{adminStats.period.newReports}</strong></article>
                        <article className="admin-stats-card"><h4><Icon name="user" /> Rejestracje - osoby prywatne</h4><strong>{adminStats.period.newPersonalAccounts}</strong></article>
                        <article className="admin-stats-card"><h4><Icon name="user" /> Rejestracje - firmy</h4><strong>{adminStats.period.newCompanyAccounts}</strong></article>
                        <article className="admin-stats-card"><h4><Icon name="shield" /> Użytkownicy oczekujący weryfikacji</h4><strong>{adminStats.summary.pendingVerificationUsers}</strong></article>
                      </div>

                      <section className="admin-stats-history">
                        <header>
                          <h3>Statystyki dzienne</h3>
                          {formattedGeneratedAt && <small>Ostatnia aktualizacja: {formattedGeneratedAt}</small>}
                        </header>
                        <div className="admin-table admin-table-stats-history" role="table">
                          <div className="admin-row admin-row-head" role="row">
                            <span>Dzień</span>
                            <span>Wiadomości</span>
                            <span>Nowe ogłoszenia</span>
                            <span>Nowe zgłoszenia</span>
                            <span>Konta prywatne</span>
                            <span>Konta firmowe</span>
                          </div>
                          {adminStats.daily.map((item) => (
                            <div className="admin-row" role="row" key={item.date}>
                              <span>{new Date(`${item.date}T00:00:00`).toLocaleDateString('pl-PL')}</span>
                              <span>{item.messagesToAdmin}</span>
                              <span>{item.newListings}</span>
                              <span>{item.newReports}</span>
                              <span>{item.newPersonalAccounts}</span>
                              <span>{item.newCompanyAccounts}</span>
                            </div>
                          ))}
                        </div>
                      </section>
                    </>
                  )}
                </div>
              </div>
            </div>
          )}
          {!loading && tab === 'settings' && <AdminPlaceholderCard icon="gear" label="Ustawienia serwisu" />}
          {!loading && tab === 'moderation' && <AdminPlaceholderCard icon="check" label="Weryfikacja ogłoszeń" />}
          {!loading && tab === 'forbiddenWords' && (
            <div className="admin-card admin-forbidden-words">
              <div className="admin-card-head">
                <h2>Słowa zakazane globalnie</h2>
                <span className="admin-forbidden-count">
                  {forbiddenSearch.trim()
                    ? `${filteredForbiddenWords.length} z ${forbiddenWords.length}`
                    : `${forbiddenWords.length} słów`}
                </span>
              </div>

              <div className="admin-forbidden-add">
                <input
                  type="text"
                  value={newForbiddenWord}
                  onChange={(event) => setNewForbiddenWord(event.target.value)}
                  onKeyDown={(event) => {
                    if (event.key === 'Enter') {
                      event.preventDefault();
                      handleAddForbiddenWord();
                    }
                  }}
                  placeholder="Dodaj nowe słowo zakazane"
                  maxLength={120}
                />
                <button
                  type="button"
                  className="admin-btn approve"
                  onClick={handleAddForbiddenWord}
                  disabled={!newForbiddenWord.trim() || savingForbiddenWord}
                >
                  {savingForbiddenWord ? 'Zapisywanie...' : 'Dodaj słowo'}
                </button>
              </div>

              <div className="admin-forbidden-search">
                <Icon name="search" />
                <input
                  type="search"
                  value={forbiddenSearch}
                  onChange={(event) => setForbiddenSearch(event.target.value)}
                  placeholder="Szukaj słowa na liście..."
                  aria-label="Szukaj słowa zakazanego"
                />
                {forbiddenSearch && (
                  <button
                    type="button"
                    className="admin-forbidden-search-clear"
                    onClick={() => setForbiddenSearch('')}
                    aria-label="Wyczyść wyszukiwanie"
                  >
                    ×
                  </button>
                )}
              </div>

              {forbiddenWords.length === 0 ? (
                <p className="admin-empty">Brak słów na liście.</p>
              ) : filteredForbiddenWords.length === 0 ? (
                <p className="admin-empty">Brak słów pasujących do „{forbiddenSearch.trim()}".</p>
              ) : (
                <div className="admin-forbidden-list" role="list">
                  {filteredForbiddenWords.map((item) => (
                    <span key={item.id} className="admin-forbidden-chip" role="listitem">
                      <span>{item.word}</span>
                      <button
                        type="button"
                        className="admin-forbidden-remove"
                        onClick={() => handleDeleteForbiddenWord(item)}
                        disabled={deletingWordId !== null}
                        aria-label={`Usuń słowo ${item.word}`}
                        title="Usuń słowo"
                      >
                        <Icon name="trash" />
                      </button>
                    </span>
                  ))}
                </div>
              )}
            </div>
          )}
          {!loading && tab === 'categories' && <AdminPlaceholderCard icon="list" label="Kategorie" />}
          {!loading && tab === 'locations' && <AdminPlaceholderCard icon="pin" label="Lokalizacje" />}
          {!loading && tab === 'promotions' && <AdminPlaceholderCard icon="spark" label="Banery i promocje" />}
          {!loading && tab === 'leads' && <AdminLeadsView Icon={Icon} />}
          {!loading && tab === 'campaigns' && <AdminCampaignsView Icon={Icon} />}
          {!loading && tab === 'mailConfig' && <AdminMailConfigView Icon={Icon} />}
        </div>
      </div>
    </section>
  );
}

function DistrictRankingPage() {
  const navigate = useNavigate();
  const [rankingMetric, setRankingMetric] = useState<DistrictRankingMetric>('overall');
  const [propertyType, setPropertyType] = useState<DistrictPropertyType>('flat');
  const [marketType, setMarketType] = useState<DistrictMarketType>('all');
  const [cityFilter, setCityFilter] = useState('all');
  const [period, setPeriod] = useState<DistrictPeriod>('12');
  const [districtMapCenter, setDistrictMapCenter] = useState<LatLng>(POLAND_CENTER);
  const [districtMapZoom, setDistrictMapZoom] = useState(6);
  const cityOptions = Array.from(new Set(districtRankingSources.map((item) => item.city))).sort((left, right) => left.localeCompare(right, 'pl'));
  const allModeledDistricts = districtRankingSources.map((source) => districtModelRow(source, propertyType, marketType, period));
  const filteredDistricts = allModeledDistricts.filter((row) => cityFilter === 'all' || row.city === cityFilter);
  const sortedDistricts = [...filteredDistricts].sort((left, right) => {
    if (rankingMetric === 'price') {
      return right.price - left.price;
    }
    if (rankingMetric === 'popularity') {
      return right.popularity - left.popularity || right.score - left.score;
    }
    if (rankingMetric === 'rating') {
      return right.rating - left.rating || right.residentScore - left.residentScore;
    }
    return right.score - left.score || right.popularity - left.popularity;
  });
  const visibleDistricts = sortedDistricts.slice(0, 20);
  const ratingLeaders = [...filteredDistricts]
    .sort((left, right) => right.rating - left.rating || right.residentScore - left.residentScore || right.score - left.score)
    .slice(0, 5);
  const districtMapMarkers: MapMarker[] = visibleDistricts.map((row, index) => ({
    id: index + 1,
    lat: row.lat,
    lng: row.lng,
    label: String(index + 1),
    ariaLabel: `#${index + 1} ${row.district}, ${row.city}`,
    offer: {
      title: `${row.city}, ${row.district}`,
      city: row.city,
      district: `${row.city}, ${row.district}`,
      rooms: 3,
      area: 55,
      priceValue: row.price,
      market: 'wtorny',
      tags: ['Ranking'],
      image: 'city',
      meta: `Cena średnia: ${formatPln(row.price)} / m²`,
    },
  }));

  return (
    <section className="district-ranking-page" aria-label="Ranking dzielnic">
      <div className="district-ranking-shell">
        <div className="district-breadcrumb">
          <Link to={ROUTES.account}>Panel główny</Link>
          <Icon name="chevron" />
          <Link className="active" to={ROUTES.districtRanking}>Ranking dzielnic</Link>
        </div>

        <div className="district-ranking-title">
          <h1>Ranking dzielnic w Polsce</h1>
          <p>Top 20 dzielnic i osiedli na podstawie median cen SonarHome z czerwca 2026 oraz wskaźników popytu, płynności i jakości życia liczonych dla 2026.</p>
        </div>

        <section className="district-filter-card" aria-label="Filtry rankingu dzielnic">
          <label>
            <span>Ranking według</span>
            <select value={rankingMetric} onChange={(event) => setRankingMetric(event.target.value as DistrictRankingMetric)}><option value="overall">Ogólny ranking</option><option value="price">Średnia cena za m²</option><option value="popularity">Popularność</option><option value="rating">Ocena mieszkańców</option></select>
          </label>
          <label>
            <span>Rodzaj nieruchomości</span>
            <select value={propertyType} onChange={(event) => setPropertyType(event.target.value as DistrictPropertyType)}><option value="flat">Mieszkania</option><option value="house">Domy</option><option value="commercial">Lokale użytkowe</option></select>
          </label>
          <label>
            <span>Rynek</span>
            <select value={marketType} onChange={(event) => setMarketType(event.target.value as DistrictMarketType)}><option value="all">Wtórny i pierwotny</option><option value="secondary">Wtórny</option><option value="primary">Pierwotny</option></select>
          </label>
          <label>
            <span>Miasto</span>
            <select value={cityFilter} onChange={(event) => setCityFilter(event.target.value)}><option value="all">Wszystkie miasta</option>{cityOptions.map((city) => <option value={city} key={city}>{city}</option>)}</select>
          </label>
          <label>
            <span>Okres</span>
            <select value={period} onChange={(event) => setPeriod(event.target.value as DistrictPeriod)}><option value="12">Ostatnie 12 miesięcy</option><option value="6">Ostatnie 6 miesięcy</option><option value="2026">2026 do dziś</option></select>
          </label>
          <button type="button">Pokaż ranking</button>
        </section>

        <div className="district-ranking-layout">
          <section className="district-ranking-table-card">
            <h2>Top {visibleDistricts.length} dzielnic: {DISTRICT_RANKING_LABELS[rankingMetric]}</h2>
            <div className="district-table-wrap">
              <table>
                <thead>
                  <tr>
                    <th>#</th>
                    <th>Dzielnica</th>
                    <th>Miasto</th>
                    <th>Średnia cena za m²</th>
                    <th>Trend</th>
                    <th>Popularność</th>
                    <th>Ocena mieszkańców</th>
                    <th>Wynik ogólny</th>
                  </tr>
                </thead>
                <tbody>
                  {visibleDistricts.map((row, index) => (
                    <tr key={`${row.city}-${row.district}`}>
                      <td><span className={`district-rank-number ${index < 3 ? `top-${index + 1}` : ''}`}>{index + 1}</span></td>
                      <td><strong>{row.district}</strong></td>
                      <td>{row.city}</td>
                      <td><strong>{formatPln(row.price)}</strong></td>
                      <td className="district-change-cell"><span className="district-change">{row.change} <Icon name="trend" /></span></td>
                      <td><span className="district-popularity"><em>{row.popularity} / 100</em><i style={{ width: `${Math.round(row.popularity * 0.82)}px` }} /></span></td>
                      <td><span className="district-rating">{row.ratingText} <Icon name="star" /></span></td>
                      <td><span className="district-score">{row.score}/100</span></td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
            <button className="district-show-all" type="button">Ranking obejmuje Top 20 według danych SonarHome <Icon name="chevron" /></button>
          </section>

          <aside className="district-ranking-side">
            <section className="district-side-card">
              <h2>Najwyżej oceniane dzielnice (wg mieszkańców)</h2>
              <ol className="district-rating-list">
                {ratingLeaders.map((item, index) => (
                  <li key={`${item.city}-${item.district}`}>
                    <span>{index + 1}</span>
                    <strong>{item.district}, {item.city}</strong>
                    <em>{item.ratingText} <Icon name="star" /></em>
                  </li>
                ))}
              </ol>
            </section>

            <section className="district-side-card map-card">
              <h2>Mapa popularności dzielnic</h2>
              <div className="district-map" aria-label="Mapa popularności dzielnic w Polsce">
                <InteractiveMap
                  center={districtMapCenter}
                  zoom={districtMapZoom}
                  onChange={(nextCenter, nextZoom) => {
                    setDistrictMapCenter(nextCenter);
                    setDistrictMapZoom(nextZoom);
                  }}
                  markers={districtMapMarkers}
                  selectedId={null}
                  onSelect={() => undefined}
                />
              </div>
            </section>
          </aside>
        </div>

        <section className="district-methodology">
          <span><Icon name="chart" /></span>
          <p><strong>Ceny za m² bazują na medianach dzielnic i osiedli publikowanych przez SonarHome dla czerwca 2026.</strong><small>Popularność jest liczona z danych 2026 (popyt, płynność, dynamika cen i relacja do benchmarku miasta), a ocena mieszkańców bezpośrednio z residentScore 2026 (residentScore / 20); wynik ogólny agreguje te wskaźniki z premią inwestycyjną.</small></p>
          <button type="button">Dowiedz się więcej o metodologii <Icon name="arrow" /></button>
        </section>
      </div>
    </section>
  );
}

function NegotiationPage({
  offers,
  selectedOffer,
  onSelectOffer,
}: {
  offers: BuyOffer[];
  selectedOffer: BuyOffer | null;
  onSelectOffer: (offer: BuyOffer) => void;
}) {
  const navigate = useNavigate();
  const fallbackOffer = offers[0] ?? null;
  const activeOffer = selectedOffer ?? fallbackOffer;

  if (!activeOffer) {
    return (
      <section className="negotiation-page" aria-label="Ile możesz utargować">
        <div className="negotiation-shell">
          <div className="negotiation-breadcrumb">
            <Link to={ROUTES.account}>Panel główny</Link>
            <Icon name="chevron" />
            <Link className="active" to={ROUTES.negotiation}>Ile możesz utargować?</Link>
          </div>

          <div className="negotiation-title">
            <h1>Ile możesz utargować?</h1>
            <p>Brak ogłoszeń do analizy. Wróć do listy ofert i wybierz mieszkanie.</p>
          </div>
          <section className="negotiation-card negotiation-note">
            <strong>Brak danych</strong>
            <p>Aktualnie nie mamy żadnego ogłoszenia, dla którego można policzyć rekomendowaną cenę transakcyjną.</p>
          </section>
        </div>
      </section>
    );
  }

  const model = buildNegotiationModel(activeOffer);
  const transactions = buildComparableTransactions(activeOffer, offers);
  const selectedIndex = Math.max(0, offers.findIndex((offer) => offer === activeOffer));
  const chanceTone = model.chance.label === 'Niska' ? 'low' : model.chance.label === 'Średnia' ? 'medium' : 'high';
  const negotiationPercent = ((model.negotiationSpan / activeOffer.priceValue) * 100).toFixed(1).replace('.', ',');
  const rangeMidPrice = Math.round((model.minPrice + model.maxPrice) / 2);
  const rangeMidPerMeter = Math.round(rangeMidPrice / activeOffer.area);
  const updatedAt = new Date().toLocaleDateString('pl-PL');
  const steps = [
    'Analizujemy ofertę',
    'Badamy rynek',
    'Wyznaczamy zakres negocjacji',
    'Pomagamy w negocjacjach',
  ];

  return (
    <section className="negotiation-page" aria-label="Ile możesz utargować">
      <div className="negotiation-shell">
        <div className="negotiation-breadcrumb">
          <Link to={ROUTES.account}>Panel główny</Link>
          <Icon name="chevron" />
          <Link className="active" to={ROUTES.negotiation}>Ile możesz utargować?</Link>
        </div>

        <div className="negotiation-title">
          <h1>Ile możesz utargować?</h1>
          <p>AI podpowie realistyczną cenę transakcyjną na podstawie danych rynkowych.</p>
        </div>

        <section className="negotiation-card negotiation-picker" aria-label="Wybór ogłoszenia">
          <label htmlFor="negotiation-offer-select">Wybierz ogłoszenie do analizy:</label>
          <select
            id="negotiation-offer-select"
            value={String(selectedIndex)}
            onChange={(event) => {
              const next = offers[Number(event.target.value)];
              if (next) {
                onSelectOffer(next);
              }
            }}
          >
            {offers.map((offer, index) => (
              <option value={index} key={`${offer.city}-${offer.district}-${offer.title}-${offer.priceValue}-${index}`}>
                {offer.title} | {offer.district} | {formatPln(offer.priceValue)}
              </option>
            ))}
          </select>
        </section>

        <div className="negotiation-layout">
          <div className="negotiation-main">
            <section className="negotiation-card negotiation-offer">
              <div className="negotiation-offer-photo" style={{ backgroundImage: `url(${activeOffer.image === 'city' ? cityImage : heroImage})`, backgroundPosition: activeOffer.position }} />
              <div className="negotiation-offer-body">
                <h2>{activeOffer.title}</h2>
                <p>{activeOffer.district}</p>
                <small>{offerMeta(activeOffer)}</small>
              </div>
              <div className="negotiation-price">
                <span>Cena ofertowa</span>
                <strong>{formatPln(activeOffer.priceValue)}</strong>
                <em>{formatPln(model.offerPerMeter).replace(' zł', '')} zł/m²</em>
              </div>
            </section>

            <section className="negotiation-card negotiation-range">
              <div className="negotiation-range-head">
                <div>
                  <p>Rekomendowana cena transakcyjna</p>
                  <h3>{formatPln(model.minPrice)} - {formatPln(model.maxPrice)}</h3>
                  <small>{formatPln(model.minPerMeter).replace(' zł', '')} - {formatPln(model.maxPerMeter).replace(' zł', '')} zł/m²</small>
                  <em className="negotiation-range-badge">Realistyczna cena, którą możesz zaproponować sprzedającemu.</em>
                </div>
                <aside className={`negotiation-chance chance-${chanceTone}`}>
                  <span>Szansa na zakup</span>
                  <strong>{model.chance.label}</strong>
                  <small>{model.chance.text}</small>
                  <div className="negotiation-gauge" aria-hidden="true">
                    <i />
                  </div>
                </aside>
              </div>
              <div className="negotiation-line" aria-hidden="true">
                <span className="dot left" />
                <span className="dot middle" />
                <span className="dot right" />
              </div>
              <div className="negotiation-line-meta">
                <div>
                  <strong>{formatPln(model.minPrice)}</strong>
                  <small>Cena minimalna</small>
                </div>
                <div>
                  <strong>{formatPln(model.negotiationSpan)}</strong>
                  <small>Zakres negocjacji ({negotiationPercent}%)</small>
                </div>
                <div>
                  <strong>{formatPln(activeOffer.priceValue)}</strong>
                  <small>Cena ofertowa</small>
                </div>
              </div>
            </section>

            <section className="negotiation-card negotiation-why">
              <h3>Dlaczego właśnie taki zakres?</h3>
              <div className="negotiation-why-grid">
                <article><span><Icon name="chart" /></span><div><strong>Analiza rynku</strong><small>Średnia cena transakcyjna podobnych ofert: {formatPln(model.marketAvgPerMeter).replace(' zł', '')} zł/m² ({model.marketDeltaPct.toString().replace('.', ',')}% vs cena ofertowa).</small></div></article>
                <article><span><Icon name="spark" /></span><div><strong>Stan mieszkania</strong><small>Model wskazuje potencjał korekty o ok. {model.conditionDiscountPct.toString().replace('.', ',')}% zależnie od wieku budynku i standardu rynku.</small></div></article>
                <article><span><Icon name="target" /></span><div><strong>Czas na rynku</strong><small>Szacowany czas sprzedaży podobnych lokali: około {model.marketDays} dni. Dłuższa ekspozycja zwiększa przestrzeń negocjacji.</small></div></article>
                <article><span><Icon name="trend" /></span><div><strong>Sezonowość</strong><small>Aktualna dynamika popytu daje dodatkowo około {model.seasonNegotiationBonusPct.toString().replace('.', ',')}% pola do rozmowy o cenie.</small></div></article>
              </div>
            </section>

            <section className="negotiation-card negotiation-note">
              <strong>Pamiętaj</strong>
              <p>Najlepszą skuteczność daje oferta startowa blisko {formatPln(model.minPrice)} i kontrpropozycja końcowa w okolicy {formatPln(rangeMidPrice)} ({formatPln(rangeMidPerMeter).replace(' zł', '')} zł/m²).</p>
            </section>

            <section className="negotiation-card negotiation-cta">
              <div>
                <strong>Potrzebujesz pomocy w negocjacjach?</strong>
                <p>Nasi eksperci pomogą Ci przejść przez proces zakupu i wynegocjować najlepszą cenę.</p>
              </div>
              <div className="negotiation-cta-action">
                <Link to={ROUTES.services}>Skontaktuj się z ekspertem</Link>
                <small>Pierwsza konsultacja gratis</small>
              </div>
            </section>

            <section className="negotiation-card negotiation-footer">
              <span><Icon name="shield" /></span>
              <p>Dane aktualizowane codziennie na podstawie tysięcy transakcji z całej Polski.</p>
              <small>Ostatnia aktualizacja: {updatedAt}</small>
            </section>
          </div>

          <aside className="negotiation-side">
            <section className="negotiation-card">
              <h3>Jak to działa?</h3>
              <ol className="negotiation-steps">
                {steps.map((step) => (
                  <li key={step}>{step}</li>
                ))}
              </ol>
            </section>

            <section className="negotiation-card">
              <h3>Podobne transakcje w okolicy</h3>
              <div className="negotiation-transactions">
                {transactions.map((tx) => (
                  <article key={tx.street}>
                    <div className="photo" style={{ backgroundImage: `url(${tx.image === 'city' ? cityImage : heroImage})` }} />
                    <div>
                      <strong>{tx.street}</strong>
                      <small>{tx.meta}</small>
                      <em>{tx.date}</em>
                    </div>
                    <div className="price">
                      <strong>{tx.price}</strong>
                      <small>{tx.sqm}</small>
                    </div>
                  </article>
                ))}
              </div>
              <button type="button" className="ghost">Zobacz więcej transakcji</button>
            </section>

            <section className="negotiation-card tip">
              <strong>Wskazówka negocjacyjna</strong>
              <p>{model.chance.label === 'Wysoka' ? 'Zacznij od dolnej granicy i zwiększaj ofertę małymi krokami (5-10 tys. zł).' : model.chance.label === 'Średnia' ? 'Zacznij od środka zakresu i przygotuj argumenty: porównywalne transakcje, czas na rynku, koszty remontu.' : 'Rozpocznij bliżej górnej granicy i negocjuj warunki dodatkowe, np. termin wydania lub wyposażenie.'}</p>
            </section>
          </aside>
        </div>
      </div>
    </section>
  );
}

function AccountSidebar() {
  const { user } = useAuth();
  const location = useLocation();
  // Ustawienia obejmuja takze podstrony profilu - podswietlamy je wspolna pozycja menu.
  const isSettingsSection = location.pathname === ROUTES.accountSettings
    || location.pathname === ROUTES.accountProfileEdit
    || location.pathname === ROUTES.accountPublicProfile;
  const [messagesUnreadCount, setMessagesUnreadCount] = useState(0);
  const allListingsCount = (() => {
    if (typeof window === 'undefined') {
      return 0;
    }
    try {
      const raw = window.localStorage.getItem(listingsStorageKey);
      if (!raw) {
        return 0;
      }
      const parsed = JSON.parse(raw);
      if (!Array.isArray(parsed)) {
        return 0;
      }
      return parsed.filter((item) => item && item.status !== 'archived').length;
    } catch {
      return 0;
    }
  })();
  const savedSearchCount = user
    ? readUserJsonStorage<SavedSearchRecord[]>(user, SAVED_SEARCHES_STORAGE_SUFFIX, []).length
    : 0;
  const favoritesCount = user
    ? readUserJsonStorage<FavoriteListing[]>(user, FAVORITE_LISTINGS_STORAGE_SUFFIX, []).length
    : 0;
  const priceAlertsCount = user
    ? readUserJsonStorage<CityPriceAlertSubscription[]>(user, CITY_PRICE_ALERTS_STORAGE_SUFFIX, []).length
    : 0;

  useEffect(() => {
    if (!user || user.role === 'ADMIN') {
      setMessagesUnreadCount(0);
      return;
    }

    let cancelled = false;
    const loadUnread = () => {
      apiFetch<{ count: number }>('/messages/unread-count')
        .then((data) => {
          if (!cancelled) {
            setMessagesUnreadCount(data.count);
          }
        })
        .catch(() => {
          if (!cancelled) {
            setMessagesUnreadCount(0);
          }
        });
    };

    loadUnread();
    const intervalId = window.setInterval(loadUnread, 25000);

    return () => {
      cancelled = true;
      window.clearInterval(intervalId);
    };
  }, [user]);

  return (
    <aside className="account-sidebar">
      <div className="account-profile-card">
        <div className="account-avatar">{getInitials(getDisplayName(user))}</div>
        <div>
          <strong>{getDisplayName(user)}</strong>
          <small>{user?.email}</small>
          <em className={user?.verified ? 'verified' : 'pending'}>
            {user?.verified ? 'Zweryfikowane konto' : 'Oczekuje na weryfikację'}
          </em>
        </div>
      </div>

      <div className="account-menu">
        <h3>Moje aktywności</h3>
        <NavLink to={ROUTES.account} className={navLinkClass}><Icon name="house" /> Panel główny</NavLink>
        <NavLink to={ROUTES.accountSearches} className={navLinkClass}><Icon name="search" /> Zapisane wyszukiwania <span>{savedSearchCount}</span></NavLink>
        <NavLink to={ROUTES.favorites} className={navLinkClass}><Icon name="heart" /> Ulubione ogłoszenia <span>{favoritesCount}</span></NavLink>
        <NavLink to={ROUTES.accountPriceAlerts} className={navLinkClass}><Icon name="bell" /> Alerty cenowe <span>{priceAlertsCount}</span></NavLink>
        <NavLink to={ROUTES.comparison} className={navLinkClass}><Icon name="shuffle" /> Porównanie mieszkań</NavLink>
      </div>

      <div className="account-menu">
        <h3>Moje transakcje</h3>
        <NavLink to={ROUTES.messages} className={navLinkClass}><Icon name="message" /> Zapytania i wiadomości <span>{messagesUnreadCount}</span></NavLink>
        <NavLink to={ROUTES.accountMeetings} className={navLinkClass}><Icon name="calendar" /> Umówione spotkania</NavLink>
        <NavLink to={ROUTES.accountListings} className={navLinkClass}><Icon name="document" /> Moje oferty <span className="tag">{allListingsCount}</span></NavLink>
      </div>

      <div className="account-menu">
        <h3>Moje narzędzia</h3>
        <NavLink to={ROUTES.creditCalculator} className={navLinkClass}><Icon name="money" /> Zdolność kredytowa</NavLink>
        <NavLink to={ROUTES.priceHistory} className={navLinkClass}><Icon name="chart" /> Historia cen</NavLink>
        <NavLink to={ROUTES.negotiation} className={navLinkClass}><Icon name="coin" /> Ile możesz utargować?</NavLink>
        <NavLink to={ROUTES.valuation} className={navLinkClass}><Icon name="calculator" /> Wyceny mieszkań</NavLink>
        <NavLink to={ROUTES.districtRanking} className={navLinkClass}><Icon name="house" /> Ranking dzielnic</NavLink>
      </div>

      <div className="account-menu">
        <h3>Ustawienia</h3>
        <NavLink to={ROUTES.accountSettings} className={isSettingsSection ? 'active' : ''}><Icon name="gear" /> Dane i ustawienia</NavLink>
        <NavLink to={ROUTES.notifications} className={navLinkClass}><Icon name="bell" /> Powiadomienia</NavLink>
        <NavLink to={ROUTES.accountSecurity} className={navLinkClass}><Icon name="shield" /> Bezpieczeństwo</NavLink>
        <NavLink to={ROUTES.accountHelpContact} className={navLinkClass}><Icon name="phone" /> Pomoc i kontakt</NavLink>
      </div>

      <div className="account-premium-card">
        <div>
          <strong><Icon name="coin" /> Konto Premium</strong>
          <p>Zyskaj dostęp do dodatkowych narzędzi i raportów.</p>
        </div>
        <button type="button">Sprawdź korzyści</button>
      </div>
    </aside>
  );
}

function AccountDashboardPage({
  savedSearches,
  onOpenSavedSearch,
}: {
  savedSearches: SavedSearchRecord[];
  onOpenSavedSearch: (savedSearch: SavedSearchRecord) => void;
}) {
  const navigate = useNavigate();
  const { user } = useAuth();
  type DashboardWidgetId = 'recent' | 'alerts' | 'searches' | 'tools';
  type DashboardWidgetConfig = {
    id: DashboardWidgetId;
    label: string;
    visible: boolean;
    layout: 'normal' | 'wide';
    order: number;
  };

  const dashboardWidgetsStorageKey = 'polskalokalnie-dashboard-widgets';
  const activityItems = [
    { icon: 'search', label: 'Zapisane wyszukiwania', count: savedSearches.length, action: 'Zobacz wszystkie' },
    { icon: 'heart', label: 'Ulubione ogłoszenia', count: 12, action: 'Zobacz wszystkie' },
    { icon: 'bell', label: 'Aktywne alerty cenowe', count: 3, action: 'Zarządzaj alertami' },
    { icon: 'message', label: 'Nieprzeczytane wiadomości', count: 4, action: 'Zobacz wiadomości' },
  ];

  const alerts = [
    { place: 'Mokotów, Stegny', meta: 'Mieszkania 3-pokojowe, 50-70 m²', price: 'Do 700 000 zł' },
    { place: 'Praga-Południe, Grochów', meta: 'Mieszkania 2-pokojowe, 35-55 m²', price: 'Do 600 000 zł' },
    { place: 'Wola, Odolany', meta: 'Mieszkania 3-4 pokojowe, 60-80 m²', price: 'Do 900 000 zł' },
  ];
  const [activeAlerts, setActiveAlerts] = useState<boolean[]>(() => alerts.map(() => true));

  const tools = [
    { icon: 'money', title: 'Zdolność kredytowa', text: 'Sprawdź, na jaki kredyt Cię stać i jaka będzie rata', path: ROUTES.creditCalculator },
    { icon: 'chart', title: 'Historia cen', text: 'Sprawdź jak zmieniały się ceny mieszkań w czasie', path: ROUTES.priceHistory },
    { icon: 'coin', title: 'Ile możesz utargować?', text: 'AI podpowie realistyczną cenę transakcyjną', path: ROUTES.negotiation },
    { icon: 'calculator', title: 'Wycena mieszkania', text: 'Darmowa wycena Twojego mieszkania online', path: ROUTES.valuation },
    { icon: 'house', title: 'Ranking dzielnic', text: 'Zobacz najlepsze dzielnice w Polsce', path: ROUTES.districtRanking },
    { icon: 'shuffle', title: 'Porównanie mieszkań', text: 'Porównuj oferty i wybieraj najlepsze', path: ROUTES.comparison },
  ];

  const recentListings = [
    {
      title: 'Mieszkanie 3-pokojowe',
      district: 'Warszawa, Mokotów, Stegny',
      priceLabel: '749 000 zł',
      metaLabel: '62 m²',
      roomsLabel: '3 pokoje',
      floorLabel: '4/8 piętro',
      perMeterLabel: '12 081 zł/m²',
      image: heroImage,
      position: '47% 58%',
    },
    {
      title: 'Mieszkanie 2-pokojowe',
      district: 'Warszawa, Praga-Południe',
      priceLabel: '699 000 zł',
      metaLabel: '45 m²',
      roomsLabel: '2 pokoje',
      floorLabel: '3/6 piętro',
      perMeterLabel: '15 533 zł/m²',
      image: cityImage,
      position: '52% 55%',
    },
    {
      title: 'Mieszkanie 4-pokojowe',
      district: 'Warszawa, Wola, Odolany',
      priceLabel: '999 000 zł',
      metaLabel: '45 m²',
      roomsLabel: '2 pokoje',
      floorLabel: '5/7 piętro',
      perMeterLabel: '13 320 zł/m²',
      image: heroImage,
      position: '72% 52%',
    },
  ];

  const defaultDashboardWidgets: DashboardWidgetConfig[] = [
    { id: 'recent', label: 'Ostatnio oglądane', visible: false, layout: 'normal', order: 0 },
    { id: 'alerts', label: 'Alerty cenowe', visible: false, layout: 'normal', order: 1 },
    { id: 'searches', label: 'Moje wyszukiwania', visible: false, layout: 'normal', order: 2 },
    { id: 'tools', label: 'Narzędzia Mieszko', visible: false, layout: 'normal', order: 3 },
  ];

  const widgetCardMeta: Record<DashboardWidgetId, { icon: string; description: string; tone: 'green' | 'blue' | 'pink' | 'amber' }> = {
    recent: { icon: 'house', description: 'Podgląd ostatnio oglądanych ofert i mieszkań.', tone: 'green' },
    searches: { icon: 'search', description: 'Twoje zapisane wyszukiwania i nowe dopasowania.', tone: 'blue' },
    alerts: { icon: 'bell', description: 'Alerty cenowe i ważne powiadomienia z rynku.', tone: 'amber' },
    tools: { icon: 'gear', description: 'Szybki dostęp do narzędzi Mieszko i analiz.', tone: 'pink' },
  };

  const [showWidgetEditLauncher, setShowWidgetEditLauncher] = useState(false);
  const [isWidgetEditorOpen, setIsWidgetEditorOpen] = useState(false);
  const [draggedWidgetId, setDraggedWidgetId] = useState<DashboardWidgetId | null>(null);
  const [dragOverWidgetId, setDragOverWidgetId] = useState<DashboardWidgetId | null>(null);
  const [dashboardWidgets, setDashboardWidgets] = useState<DashboardWidgetConfig[]>(() => {
    try {
      const raw = window.localStorage.getItem(dashboardWidgetsStorageKey);
      if (!raw) {
        return defaultDashboardWidgets;
      }
      const parsed = JSON.parse(raw);
      if (!Array.isArray(parsed)) {
        return defaultDashboardWidgets;
      }

      const merged = defaultDashboardWidgets.map((fallback) => {
        const current = parsed.find((item) => item && item.id === fallback.id);
        return {
          ...fallback,
          visible: typeof current?.visible === 'boolean' ? current.visible : fallback.visible,
          layout: (current?.layout === 'wide' ? 'wide' : 'normal') as 'normal' | 'wide',
          order: typeof current?.order === 'number' ? current.order : fallback.order,
        };
      });

      return merged.sort((left, right) => left.order - right.order).map((item, index) => ({ ...item, order: index }));
    } catch {
      return defaultDashboardWidgets;
    }
  });

  useEffect(() => {
    window.localStorage.setItem(dashboardWidgetsStorageKey, JSON.stringify(dashboardWidgets));
  }, [dashboardWidgets]);

  const sortedWidgets = [...dashboardWidgets].sort((left, right) => left.order - right.order);

  const updateWidget = (id: DashboardWidgetId, changes: Partial<DashboardWidgetConfig>) => {
    setDashboardWidgets((current) => current.map((widget) => (widget.id === id ? { ...widget, ...changes } : widget)));
  };

  const moveWidget = (id: DashboardWidgetId, direction: -1 | 1) => {
    setDashboardWidgets((current) => {
      const sorted = [...current].sort((left, right) => left.order - right.order);
      const index = sorted.findIndex((widget) => widget.id === id);
      const nextIndex = index + direction;
      if (index < 0 || nextIndex < 0 || nextIndex >= sorted.length) {
        return current;
      }
      const copy = [...sorted];
      [copy[index], copy[nextIndex]] = [copy[nextIndex], copy[index]];
      return copy.map((item, order) => ({ ...item, order }));
    });
  };

  const reorderWidgets = (sourceId: DashboardWidgetId, targetId: DashboardWidgetId) => {
    if (sourceId === targetId) {
      return;
    }

    setDashboardWidgets((current) => {
      const sorted = [...current].sort((left, right) => left.order - right.order);
      const sourceIndex = sorted.findIndex((item) => item.id === sourceId);
      const targetIndex = sorted.findIndex((item) => item.id === targetId);
      if (sourceIndex < 0 || targetIndex < 0) {
        return current;
      }

      const next = [...sorted];
      const [moved] = next.splice(sourceIndex, 1);
      next.splice(targetIndex, 0, moved);
      return next.map((item, order) => ({ ...item, order }));
    });
  };

  const restoreDefaultWidgets = () => {
    setDashboardWidgets(defaultDashboardWidgets);
  };

  const openWidgetEditor = () => {
    setShowWidgetEditLauncher(true);
    setIsWidgetEditorOpen(true);
    window.requestAnimationFrame(() => {
      window.scrollTo({ top: 0, behavior: 'smooth' });
    });
  };

  const widgetSections: Record<DashboardWidgetId, ReactNode> = {
    recent: (
      <section className="account-panel" key="recent">
        <div className="account-panel-head">
          <h2>Ostatnio oglądane</h2>
          <button type="button">Zobacz wszystkie <Icon name="arrow" /></button>
        </div>
        <div className="account-listings">
          {recentListings.map((listing) => (
            <article key={`${listing.title}-${listing.district}`}>
              <div className="photo" style={{ backgroundImage: `url(${listing.image})`, backgroundPosition: listing.position }} />
              <strong>{listing.priceLabel}</strong>
              <h3>{listing.title}</h3>
              <p>{listing.district}</p>
              <small>{listing.metaLabel} • {listing.roomsLabel} • {listing.floorLabel}</small>
              <em>{listing.perMeterLabel}</em>
            </article>
          ))}
        </div>
      </section>
    ),
    alerts: (
      <section className="account-panel" key="alerts">
        <div className="account-panel-head">
          <h2>Alerty cenowe</h2>
          <button type="button">Zarządzaj alertami <Icon name="arrow" /></button>
        </div>
        <div className="account-alerts">
          {alerts.map((alert, index) => (
            <article key={alert.place}>
              <span><Icon name="chart" /></span>
              <div>
                <strong>{alert.place}</strong>
                <small>{alert.meta}</small>
                <em>{alert.price}</em>
              </div>
              <button
                type="button"
                className={`account-toggle ${activeAlerts[index] ? 'on' : ''}`}
                aria-label={`Przełącz alert: ${alert.place}`}
                aria-pressed={activeAlerts[index]}
                onClick={() => {
                  setActiveAlerts((current) => current.map((value, idx) => (idx === index ? !value : value)));
                }}
              />
            </article>
          ))}
          <button type="button" className="account-add-alert">Utwórz nowy alert <Icon name="plus" /></button>
        </div>
      </section>
    ),
    searches: (
      <section className="account-panel" key="searches">
        <div className="account-panel-head">
          <h2>Moje wyszukiwania</h2>
          <Link to={ROUTES.accountSearches}>Zobacz wszystkie <Icon name="arrow" /></Link>
        </div>
        <div className="account-searches">
          {savedSearches.slice(0, 4).map((row) => (
            <article
              key={row.id}
              role="button"
              tabIndex={0}
              onClick={() => onOpenSavedSearch(row)}
              onKeyDown={(event) => {
                if (event.key === 'Enter' || event.key === ' ') {
                  event.preventDefault();
                  onOpenSavedSearch(row);
                }
              }}
            >
              <span><Icon name="search" /></span>
              <div>
                <strong>{savedSearchPlaceLabel(row)}</strong>
                <small>{savedSearchMetaLabel(row)}</small>
              </div>
              <em>Zaktualizowano<br />{savedSearchUpdatedLabel(row.updatedAt)}</em>
              <b>{savedSearchModeLabel(row.mode)}<br />Tryb</b>
              <button type="button" aria-label={`Otwórz wyszukiwanie: ${savedSearchPlaceLabel(row)}`} onClick={(event) => { event.stopPropagation(); onOpenSavedSearch(row); }}><Icon name="chevron" /></button>
            </article>
          ))}
          {savedSearches.length === 0 && <p className="account-searches-empty">Nie masz jeszcze zapisanych wyszukiwań. Wejdź w Kup lub Wynajem i kliknij dzwonek „Zapisz wyszukiwanie”.</p>}
          <Link className="account-new-search" to={ROUTES.buy}>Utwórz nowe wyszukiwanie <Icon name="plus" /></Link>
        </div>
      </section>
    ),
    tools: (
      <section className="account-panel" key="tools">
        <div className="account-panel-head">
          <h2>Narzędzia Mieszko</h2>
        </div>
        <div className="account-tools">
          {tools.map((tool) => (
            // Kafelek prowadzi na inna strone, wiec jest linkiem - bez protezy role="button".
            <Link key={tool.title} to={tool.path}>
              <span><Icon name={tool.icon} /></span>
              <div>
                <strong>{tool.title}</strong>
                <small>{tool.text}</small>
              </div>
            </Link>
          ))}
        </div>
        <button type="button" className="account-tools-more">Zobacz wszystkie narzędzia <Icon name="arrow" /></button>
      </section>
    ),
  };

  return (
    <section className="account-page" aria-label="Panel konta użytkownika">
      <div className="account-layout">
        <AccountSidebar />

        <div className="account-main">
          <div className="account-heading">
            <div>
              <h1>Witaj, {getFirstName(getDisplayName(user))}! <span aria-hidden="true">👋</span></h1>
              <p>Zarządzaj swoimi wyszukiwaniami, ofertami i ulubionymi ogłoszeniami.</p>
            </div>
            <button
              type="button"
              onClick={() => {
                if (isWidgetEditorOpen) {
                  setShowWidgetEditLauncher(false);
                  setIsWidgetEditorOpen(false);
                } else {
                  openWidgetEditor();
                }
              }}
            >
              <Icon name="gear" /> Edytuj profil
            </button>
          </div>

          {sortedWidgets.some((widget) => widget.visible) && (
            <div className="account-grid">
              {sortedWidgets.filter((widget) => widget.visible).map((widget) => (
                <div key={widget.id} className={widget.layout === 'wide' ? 'account-widget-slot wide' : 'account-widget-slot'}>
                  {widgetSections[widget.id]}
                </div>
              ))}
            </div>
          )}

          {isWidgetEditorOpen && (
            <div className="account-grid account-grid-empty editor-open">
              <div className="account-widget-customizer centered">
                <div className="account-widget-customizer-head">
                  <div>
                    <strong>Dostępne widżety</strong>
                    <small>Wybierz widżety, które chcesz dodać do swojego panelu.</small>
                  </div>
                  <div className="account-widget-customizer-head-actions">
                    <button type="button" onClick={restoreDefaultWidgets}><Icon name="shuffle" /> Przywróć domyślny układ</button>
                    <button
                      type="button"
                      onClick={() => {
                        setIsWidgetEditorOpen(false);
                        setShowWidgetEditLauncher(false);
                      }}
                    >
                      Zamknij
                    </button>
                  </div>
                </div>

                <section className="account-widget-catalog" aria-label="Katalog widżetów">
                  {sortedWidgets.map((widget) => {
                    const meta = widgetCardMeta[widget.id];
                    return (
                      <article key={widget.id} className={`account-widget-card ${meta.tone}`}>
                        <span><Icon name={meta.icon} /></span>
                        <div>
                          <strong>{widget.label}</strong>
                          <small>{meta.description}</small>
                        </div>
                        <button type="button" onClick={() => updateWidget(widget.id, { visible: !widget.visible })}>
                          <Icon name="plus" /> {widget.visible ? 'Usuń' : 'Dodaj'}
                        </button>
                      </article>
                    );
                  })}
                </section>

                <div className="account-widget-tip">
                  <Icon name="spark" />
                  <span>Wskazówka: przeciągaj widżety poniżej, aby szybko zmieniać ich kolejność na panelu.</span>
                </div>

                <div className="account-widget-customizer-head secondary">
                  <strong>Kolejność i układ aktywnych widżetów</strong>
                </div>

                <section className="account-widget-editor" aria-label="Ustawienia widżetów panelu głównego">
                  {sortedWidgets.filter((widget) => widget.visible).map((widget) => (
                    <article
                      key={widget.id}
                      draggable
                      className={dragOverWidgetId === widget.id ? 'drop-target' : ''}
                      onDragStart={() => {
                        setDraggedWidgetId(widget.id);
                      }}
                      onDragOver={(event) => {
                        event.preventDefault();
                        if (dragOverWidgetId !== widget.id) {
                          setDragOverWidgetId(widget.id);
                        }
                      }}
                      onDrop={(event) => {
                        event.preventDefault();
                        if (draggedWidgetId) {
                          reorderWidgets(draggedWidgetId, widget.id);
                        }
                        setDraggedWidgetId(null);
                        setDragOverWidgetId(null);
                      }}
                      onDragEnd={() => {
                        setDraggedWidgetId(null);
                        setDragOverWidgetId(null);
                      }}
                    >
                      <strong>{widget.label}</strong>
                      <div className="account-widget-editor-controls">
                        <select value={widget.layout} onChange={(event) => updateWidget(widget.id, { layout: event.target.value as 'normal' | 'wide' })}>
                          <option value="normal">Układ normalny</option>
                          <option value="wide">Pełna szerokość</option>
                        </select>
                        <button type="button" onClick={() => moveWidget(widget.id, -1)}>W górę</button>
                        <button type="button" onClick={() => moveWidget(widget.id, 1)}>W dół</button>
                        <small>Przeciągnij</small>
                      </div>
                    </article>
                  ))}
                </section>
              </div>
            </div>
          )}

          {!isWidgetEditorOpen && !sortedWidgets.some((widget) => widget.visible) ? (
            <div className="account-grid account-grid-empty">
              <div className="account-empty-add">
                <section className="account-empty-state-card" aria-label="Dostosuj panel">
                  <div className="account-empty-visual" aria-hidden="true">
                    <span className="left-chip"><Icon name="chart" /></span>
                    <div className="center-chip">
                      <Icon name="document" />
                      <i />
                    </div>
                    <span className="right-chip"><Icon name="plus" /></span>
                  </div>
                  <h2>Dostosuj swój panel</h2>
                  <p>Dodaj widżety, aby mieć szybki dostęp do tego, co dla Ciebie najważniejsze.</p>
                  <button
                    type="button"
                    className="account-empty-add-button"
                    onClick={openWidgetEditor}
                  >
                    <Icon name="plus" /> Dodaj widżety
                  </button>
                </section>
              </div>
            </div>
          ) : null}
        </div>
      </div>
    </section>
  );
}

function AccountSavedSearchesPage({
  savedSearches,
  onOpenSavedSearch,
  onRemoveSavedSearch,
}: {
  savedSearches: SavedSearchRecord[];
  onOpenSavedSearch: (savedSearch: SavedSearchRecord) => void;
  onRemoveSavedSearch: (savedSearchId: string) => void;
}) {
  const navigate = useNavigate();
  const [query, setQuery] = useState('');
  const [modeFilter, setModeFilter] = useState<'all' | SavedSearchMode>('all');
  const [sortMode, setSortMode] = useState<'newest' | 'oldest' | 'city'>('newest');

  const normalizedQuery = query.trim().toLowerCase();
  const allCount = savedSearches.length;
  const buyCount = savedSearches.filter((item) => item.mode === 'buy').length;
  const rentCount = savedSearches.filter((item) => item.mode === 'rent').length;

  const filteredSavedSearches = useMemo(() => {
    const byMode = modeFilter === 'all'
      ? savedSearches
      : savedSearches.filter((item) => item.mode === modeFilter);

    const byQuery = normalizedQuery.length === 0
      ? byMode
      : byMode.filter((item) => {
        const target = [savedSearchPlaceLabel(item), savedSearchMetaLabel(item), savedSearchModeLabel(item.mode)].join(' ').toLowerCase();
        return target.includes(normalizedQuery);
      });

    return [...byQuery].sort((left, right) => {
      if (sortMode === 'oldest') {
        return new Date(left.updatedAt).getTime() - new Date(right.updatedAt).getTime();
      }
      if (sortMode === 'city') {
        return savedSearchPlaceLabel(left).localeCompare(savedSearchPlaceLabel(right), 'pl');
      }
      return new Date(right.updatedAt).getTime() - new Date(left.updatedAt).getTime();
    });
  }, [modeFilter, normalizedQuery, savedSearches, sortMode]);

  return (
    <section className="account-page account-searches-page" aria-label="Zapisane wyszukiwania">
      <div className="account-layout">
        <AccountSidebar />

        <div className="account-main">
          <section className="account-panel account-searches-panel">
            <div className="account-panel-head account-searches-page-head">
              <div>
                <h1>Zapisane wyszukiwania</h1>
                <p>Wróć do swoich filtrów jednym kliknięciem i przejdź od razu do odpowiedniej listy ofert.</p>
              </div>
              <Link to={ROUTES.buy}>Dodaj nowe <Icon name="plus" /></Link>
            </div>

            <div className="account-searches-stats" aria-label="Statystyki zapisanych wyszukiwań">
              <article>
                <small>Wszystkie</small>
                <strong>{allCount}</strong>
              </article>
              <article>
                <small>Kupno</small>
                <strong>{buyCount}</strong>
              </article>
              <article>
                <small>Wynajem</small>
                <strong>{rentCount}</strong>
              </article>
            </div>

            <div className="account-searches-toolbar">
              <label className="account-searches-input" aria-label="Wyszukaj zapisane wyszukiwania">
                <Icon name="search" />
                <input
                  type="text"
                  value={query}
                  onChange={(event) => setQuery(event.target.value)}
                  placeholder="Szukaj po lokalizacji, metrażu, cenie..."
                />
              </label>

              <div className="account-searches-mode-tabs" role="tablist" aria-label="Filtr trybu wyszukiwania">
                <button type="button" className={modeFilter === 'all' ? 'active' : ''} onClick={() => setModeFilter('all')}>Wszystkie</button>
                <button type="button" className={modeFilter === 'buy' ? 'active' : ''} onClick={() => setModeFilter('buy')}>Kupno</button>
                <button type="button" className={modeFilter === 'rent' ? 'active' : ''} onClick={() => setModeFilter('rent')}>Wynajem</button>
              </div>

              <label className="account-searches-sort">
                <span>Sortuj</span>
                <select value={sortMode} onChange={(event) => setSortMode(event.target.value as 'newest' | 'oldest' | 'city')}>
                  <option value="newest">Najnowsze</option>
                  <option value="oldest">Najstarsze</option>
                  <option value="city">Lokalizacja A-Z</option>
                </select>
              </label>
            </div>

            <div className="account-searches">
              {filteredSavedSearches.map((savedSearch) => (
                <article
                  key={savedSearch.id}
                  role="button"
                  tabIndex={0}
                  onClick={() => onOpenSavedSearch(savedSearch)}
                  onKeyDown={(event) => {
                    if (event.key === 'Enter' || event.key === ' ') {
                      event.preventDefault();
                      onOpenSavedSearch(savedSearch);
                    }
                  }}
                >
                  <span><Icon name="search" /></span>
                  <div>
                    <strong>{savedSearchPlaceLabel(savedSearch)}</strong>
                    <small>{savedSearchMetaLabel(savedSearch)}</small>
                  </div>
                  <em><Icon name="calendar" /> {savedSearchUpdatedLabel(savedSearch.updatedAt)}</em>
                  <b className={`account-search-mode-pill ${savedSearch.mode === 'rent' ? 'rent' : 'buy'}`}>{savedSearchModeLabel(savedSearch.mode)}</b>
                  <button
                    type="button"
                    className="account-search-open"
                    aria-label={`Przejdź do wyników: ${savedSearchPlaceLabel(savedSearch)}`}
                    onClick={(event) => {
                      event.stopPropagation();
                      onOpenSavedSearch(savedSearch);
                    }}
                  >
                    Przejdź do wyników <Icon name="arrow" />
                  </button>
                  <button
                    type="button"
                    className="account-search-remove"
                    aria-label={`Usuń wyszukiwanie: ${savedSearchPlaceLabel(savedSearch)}`}
                    onClick={(event) => {
                      event.stopPropagation();
                      onRemoveSavedSearch(savedSearch.id);
                    }}
                  >
                    <Icon name="trash" /> Usuń
                  </button>
                </article>
              ))}
            </div>

            {savedSearches.length === 0 && (
              <div className="account-searches-empty-state">
                <strong>Nie masz jeszcze zapisanych wyszukiwań</strong>
                <p>Ustaw filtry w Kup lub Wynajem i kliknij dzwonek „Zapisz wyszukiwanie”, a pojawią się tutaj.</p>
                <Link className="account-empty-add-button" to={ROUTES.buy}>
                  <Icon name="search" /> Przejdź do wyszukiwania
                </Link>
              </div>
            )}

            {savedSearches.length > 0 && filteredSavedSearches.length === 0 && (
              <div className="account-searches-empty-state filtered">
                <strong>Brak wyników dla podanych filtrów</strong>
                <p>Spróbuj zmienić frazę wyszukiwania, tryb albo sortowanie.</p>
                <button
                  type="button"
                  className="account-empty-add-button"
                  onClick={() => {
                    setQuery('');
                    setModeFilter('all');
                    setSortMode('newest');
                  }}
                >
                  <Icon name="shuffle" /> Wyczyść filtry
                </button>
              </div>
            )}
          </section>
        </div>
      </div>
    </section>
  );
}

function AccountHelpContactPage({
  user,
}: {
  user: AuthUser | null;
}) {
  type HelpContactFormValues = {
    fullName: string;
    email: string;
    topic: string;
    message: string;
  };

  type HelpContactFeedback = {
    type: 'success' | 'error';
    text: string;
  };

  const supportChannels = [
    {
      id: 'live-chat',
      icon: 'message',
      iconTone: 'green',
      title: 'Czat na żywo',
      badge: 'Najszybsza pomoc',
      description: 'Porozmawiaj z naszym konsultantem w czasie rzeczywistym.',
      action: 'Dostępny 8:00 - 20:00',
    },
    {
      id: 'email',
      icon: 'mail',
      iconTone: 'blue',
      title: 'E-mail',
      description: 'Napisz do nas, odpowiemy najszybciej jak to możliwe.',
      action: 'bok@mieszko.pl',
    },
    {
      id: 'phone',
      icon: 'phone',
      iconTone: 'violet',
      title: 'Telefon',
      description: 'Zadzwoń do nas, chętnie odpowiemy na Twoje pytania.',
      action: '+48 22 123 45 67',
    },
    {
      id: 'form',
      icon: 'document',
      iconTone: 'orange',
      title: 'Formularz kontaktowy',
      description: 'Wyślij nam wiadomość, a my skontaktujemy się z Tobą.',
      action: 'Napisz poniżej',
    },
  ] as const;

  const faqItems = [
    'Jak dodać ogłoszenie?',
    'Ile kosztuje dodanie ogłoszenia?',
    'Jak wyróżnić moje ogłoszenie?',
    'Jak edytować lub usunąć ogłoszenie?',
    'Jak działa porównywarka mieszkań?',
    'Jak ustawić alerty cenowe?',
  ] as const;

  const moreHelpCards = [
    {
      id: 'guides',
      icon: 'document',
      iconTone: 'green',
      title: 'Poradniki',
      description: 'Praktyczne artykuły i wskazówki dotyczące kupna, sprzedaży i wynajmu nieruchomości.',
      action: 'Zobacz poradniki',
    },
    {
      id: 'security',
      icon: 'shield',
      iconTone: 'gold',
      title: 'Bezpieczeństwo',
      description: 'Dowiedz się jak bezpiecznie korzystać z serwisu i unikać oszustw.',
      action: 'Zasady bezpieczeństwa',
    },
    {
      id: 'rules',
      icon: 'list',
      iconTone: 'blue',
      title: 'Regulamin i zasady',
      description: 'Zapoznaj się z regulaminem serwisu oraz zasadami korzystania.',
      action: 'Przeczytaj regulamin',
    },
    {
      id: 'report',
      icon: 'warning',
      iconTone: 'red',
      title: 'Zgłoś problem',
      description: 'Zgłoś nieodpowiednie treści lub problemy techniczne.',
      action: 'Zgłoś',
    },
  ] as const;

  const [contactForm, setContactForm] = useState<HelpContactFormValues>({
    fullName: '',
    email: '',
    topic: '',
    message: '',
  });
  const [contactFeedback, setContactFeedback] = useState<HelpContactFeedback | null>(null);
  const [isSubmittingContactForm, setIsSubmittingContactForm] = useState(false);
  const [isContactFormHighlighted, setIsContactFormHighlighted] = useState(false);
  const contactFormHighlightTimeoutRef = useRef<number | null>(null);
  const contactTopicLabels: Record<string, string> = {
    ogloszenie: 'Pytanie o ogłoszenie',
    konto: 'Problemy z kontem',
    platnosci: 'Płatności i faktury',
    techniczne: 'Problem techniczny',
  };

  useEffect(() => () => {
    if (contactFormHighlightTimeoutRef.current !== null) {
      window.clearTimeout(contactFormHighlightTimeoutRef.current);
    }
  }, []);

  useEffect(() => {
    setContactForm((previous) => ({
      ...previous,
      fullName: user?.fullName ?? '',
      email: user?.email ?? '',
    }));
  }, [user?.fullName, user?.email]);

  const handleContactFieldChange = useCallback((field: keyof HelpContactFormValues, value: string) => {
    setContactForm((previous) => ({ ...previous, [field]: value }));
    setContactFeedback((previous) => (previous ? null : previous));
  }, []);

  const highlightContactFormForThreeSeconds = useCallback(() => {
    if (contactFormHighlightTimeoutRef.current !== null) {
      window.clearTimeout(contactFormHighlightTimeoutRef.current);
    }

    setIsContactFormHighlighted(false);
    window.requestAnimationFrame(() => {
      setIsContactFormHighlighted(true);
    });

    contactFormHighlightTimeoutRef.current = window.setTimeout(() => {
      setIsContactFormHighlighted(false);
      contactFormHighlightTimeoutRef.current = null;
    }, 3000);
  }, []);

  const handleContactSubmit = useCallback(async (event: ReactFormEvent<HTMLFormElement>) => {
    event.preventDefault();

    if (!user) {
      setContactFeedback({
        type: 'error',
        text: 'Aby wysłać wiadomość, zaloguj się na konto.',
      });
      return;
    }

    const trimmedName = contactForm.fullName.trim();
    const trimmedEmail = contactForm.email.trim();
    const trimmedTopic = contactForm.topic.trim();
    const trimmedMessage = contactForm.message.trim();

    if (!trimmedName || !trimmedEmail || !trimmedTopic || !trimmedMessage) {
      setContactFeedback({
        type: 'error',
        text: 'Uzupełnij wszystkie pola formularza.',
      });
      return;
    }

    const emailLooksValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmedEmail);
    if (!emailLooksValid) {
      setContactFeedback({
        type: 'error',
        text: 'Podaj poprawny adres e-mail.',
      });
      return;
    }

    setIsSubmittingContactForm(true);
    try {
      const topicLabel = contactTopicLabels[trimmedTopic] ?? trimmedTopic;
      const messageContent = [
        `[Pomoc i kontakt] ${topicLabel}`,
        `Imię i nazwisko: ${trimmedName}`,
        `E-mail: ${trimmedEmail}`,
        '',
        trimmedMessage,
      ].join('\n');

      await apiFetch('/messages/admin', {
        method: 'POST',
        body: JSON.stringify({ content: messageContent }),
      });

      setContactFeedback({
        type: 'success',
        text: 'Wiadomość została wysłana. Odpowiemy najszybciej jak to możliwe.',
      });
      setContactForm((previous) => ({
        ...previous,
        topic: '',
        message: '',
      }));
    } catch (err) {
      setContactFeedback({
        type: 'error',
        text: err instanceof Error ? err.message : 'Nie udało się wysłać wiadomości. Spróbuj ponownie.',
      });
    } finally {
      setIsSubmittingContactForm(false);
    }
  }, [contactForm, contactTopicLabels, user]);

  return (
    <section className="account-page account-help-contact-page" aria-label="Pomoc i kontakt">
      <div className="account-layout">
        <AccountSidebar />

        <div className="account-main">
          <section className="help-contact-panel">
            <header className="help-contact-header">
              <div>
                <h1>Pomoc i kontakt</h1>
                <p>Jesteśmy tutaj, aby Ci pomóc. Skontaktuj się z nami lub znajdź odpowiedzi na najczęściej zadawane pytania.</p>
              </div>
              <div className="help-contact-visual" aria-hidden="true">
                <span><Icon name="mail" /></span>
                <strong><Icon name="phone" /></strong>
                <span><Icon name="message" /></span>
              </div>
            </header>

            <section className="help-contact-section">
              <h2>Jak możemy Ci pomóc?</h2>
              <div className="help-contact-channel-grid">
                {supportChannels.map((channel) => (
                  <article key={channel.id} className="help-contact-channel-card">
                    <div className="help-contact-channel-head">
                      <span className={`help-contact-channel-icon ${channel.iconTone}`}><Icon name={channel.icon} /></span>
                      <div>
                        <h3>{channel.title}</h3>
                        {'badge' in channel && channel.badge ? <em>{channel.badge}</em> : null}
                      </div>
                    </div>
                    <p>{channel.description}</p>
                    <button
                      type="button"
                      className="help-contact-channel-action"
                      onClick={channel.id === 'form' ? highlightContactFormForThreeSeconds : undefined}
                    >
                      {channel.action}
                      <Icon name="chevron" />
                    </button>
                  </article>
                ))}
              </div>
            </section>

            <section className="help-contact-section help-contact-middle-grid">
              <article className="help-contact-faq-card">
                <header>
                  <h2>Najczęściej zadawane pytania</h2>
                  <button type="button">Zobacz wszystkie <Icon name="arrow" /></button>
                </header>
                <div className="help-contact-faq-list">
                  {faqItems.map((question) => (
                    <details key={question}>
                      <summary>
                        <span>{question}</span>
                        <Icon name="chevron" />
                      </summary>
                      <p>Znajdziesz odpowiedź w naszym centrum pomocy. W razie potrzeby skorzystaj z formularza kontaktowego obok.</p>
                    </details>
                  ))}
                </div>
              </article>

              <article className={`help-contact-form-card ${isContactFormHighlighted ? 'is-highlighted' : ''}`}>
                <h2>Napisz do nas</h2>
                <form onSubmit={handleContactSubmit}>
                  <div className="help-contact-form-row">
                    <input
                      type="text"
                      placeholder="Imię i nazwisko"
                      value={contactForm.fullName}
                      onChange={(event) => handleContactFieldChange('fullName', event.target.value)}
                    />
                    <input
                      type="email"
                      placeholder="Adres e-mail"
                      value={contactForm.email}
                      onChange={(event) => handleContactFieldChange('email', event.target.value)}
                    />
                  </div>
                  <select
                    value={contactForm.topic}
                    onChange={(event) => handleContactFieldChange('topic', event.target.value)}
                  >
                    <option value="" disabled>Temat</option>
                    <option value="ogloszenie">Pytanie o ogłoszenie</option>
                    <option value="konto">Problemy z kontem</option>
                    <option value="platnosci">Płatności i faktury</option>
                    <option value="techniczne">Problem techniczny</option>
                  </select>
                  <textarea
                    rows={5}
                    placeholder="Wiadomość&#10;Opisz swój problem lub pytanie..."
                    value={contactForm.message}
                    onChange={(event) => handleContactFieldChange('message', event.target.value)}
                  />
                  {contactFeedback ? (
                    <p className={`help-contact-form-feedback ${contactFeedback.type}`} role="status" aria-live="polite">
                      {contactFeedback.text}
                    </p>
                  ) : null}
                  <button type="submit" disabled={isSubmittingContactForm}>
                    {isSubmittingContactForm ? 'Wysyłanie...' : 'Wyślij wiadomość'}
                  </button>
                </form>
              </article>
            </section>

            <section className="help-contact-section">
              <h2>Więcej sposobów na pomoc</h2>
              <div className="help-contact-more-grid">
                {moreHelpCards.map((card) => (
                  <article key={card.id} className="help-contact-more-card">
                    <span className={`help-contact-more-icon ${card.iconTone}`}><Icon name={card.icon} /></span>
                    <div>
                      <h3>{card.title}</h3>
                      <p>{card.description}</p>
                      <button type="button">{card.action} <Icon name="arrow" /></button>
                    </div>
                  </article>
                ))}
              </div>
            </section>
          </section>
        </div>
      </div>
    </section>
  );
}

function AccountPriceAlertsPage({
  user,
  onOpenListing,
}: {
  user: AuthUser | null;
  onOpenListing: (id: number) => void;
}) {
  const navigate = useNavigate();
  const [cityAlerts, setCityAlerts] = useState<CityPriceAlertSubscription[]>([]);
  const [priceEvents, setPriceEvents] = useState<FavoritePriceAlertEvent[]>([]);
  const [cityPriceEvents, setCityPriceEvents] = useState<CityPriceAlertEvent[]>([]);

  useEffect(() => {
    if (!user) {
      setCityAlerts([]);
      setPriceEvents([]);
      return;
    }

    const hydrate = () => {
      setCityAlerts(readUserJsonStorage<CityPriceAlertSubscription[]>(user, CITY_PRICE_ALERTS_STORAGE_SUFFIX, []));
      setPriceEvents(readUserJsonStorage<FavoritePriceAlertEvent[]>(user, FAVORITE_PRICE_ALERTS_STORAGE_SUFFIX, []));
      setCityPriceEvents(readUserJsonStorage<CityPriceAlertEvent[]>(user, CITY_PRICE_ALERT_EVENTS_STORAGE_SUFFIX, []));
    };

    hydrate();
    const intervalId = window.setInterval(hydrate, 10000);
    return () => window.clearInterval(intervalId);
  }, [user]);

  const latestEvent = priceEvents[0] ?? null;
  const latestCityEvent = cityPriceEvents[0] ?? null;
  const latestEventType: 'listing' | 'city' | null = (() => {
    if (latestEvent && latestCityEvent) {
      return new Date(latestEvent.changedAt).getTime() >= new Date(latestCityEvent.changedAt).getTime()
        ? 'listing'
        : 'city';
    }
    if (latestEvent) {
      return 'listing';
    }
    if (latestCityEvent) {
      return 'city';
    }
    return null;
  })();
  const cityInsights = useMemo(() => cityAlerts.map((subscription, index) => {
    const market = HISTORY_PRICE_MARKETS.find((item) => item.key === subscription.key) ?? findHistoryMarket(subscription.label);
    const district = historyShortMarketLabel(market ?? resolveHistoryMarket(subscription.label));
    const rawPrice = market?.apartmentM2 ?? estimateHistoryLocationPrice(subscription.label).price;
    const price = Math.round(rawPrice * HISTORY_PROPERTY_MULTIPLIERS[subscription.propertyType] * HISTORY_MARKET_MULTIPLIERS[subscription.marketType]);
    const delta = (market?.trend24 ?? (0.03 + ((index % 4) * 0.008))) * 100;
    return {
      key: subscription.key,
      district,
      price,
      delta,
      barWidth: Math.max(28, 78 - (index * 9)),
    };
  }), [cityAlerts]);

  const cityAveragePrice = cityInsights.length > 0
    ? Math.round(cityInsights.reduce((sum, item) => sum + item.price, 0) / cityInsights.length)
    : 0;
  const cityAverageDelta = cityInsights.length > 0
    ? cityInsights.reduce((sum, item) => sum + item.delta, 0) / cityInsights.length
    : 0;

  const activeAlerts = cityAlerts.map((subscription) => ({
    title: subscription.label,
    meta: `${subscription.propertyType} • ${subscription.marketType}`,
    key: subscription.key,
  }));

  const removeAlert = (key: string) => {
    if (!user) {
      return;
    }
    setCityAlerts((current) => {
      const next = current.filter((item) => item.key !== key);
      writeUserJsonStorage(user, CITY_PRICE_ALERTS_STORAGE_SUFFIX, next);
      return next;
    });
  };

  const activeListingEvent = latestEventType === 'listing' ? latestEvent : null;
  const activeCityEvent = latestEventType === 'city' ? latestCityEvent : null;
  const hasPriceDrop = activeListingEvent
    ? activeListingEvent.currentPrice < activeListingEvent.previousPrice
    : activeCityEvent
      ? activeCityEvent.currentPerM2 < activeCityEvent.previousPerM2
      : false;
  const latestDelta = activeListingEvent
    ? activeListingEvent.currentPrice - activeListingEvent.previousPrice
    : activeCityEvent
      ? activeCityEvent.currentPerM2 - activeCityEvent.previousPerM2
      : 0;
  const latestDeltaPercent = (activeListingEvent && activeListingEvent.previousPrice > 0)
    ? (Math.abs(latestDelta) / activeListingEvent.previousPrice) * 100
    : (activeCityEvent && activeCityEvent.previousPerM2 > 0)
      ? (Math.abs(latestDelta) / activeCityEvent.previousPerM2) * 100
    : 0;

  return (
    <section className="account-settings-page price-alerts-page" aria-label="Powiadomienia i alerty cenowe">
      <div className="account-layout">
        <AccountSidebar />

        <div className="account-main account-settings-main">
          <div className="price-alerts-heading">
            <small>Strona główna <Icon name="arrow" /> Powiadomienia i alerty</small>
            <div>
              <h1>Powiadomienia i alerty</h1>
              <p>Śledź zmiany cen w wybranych lokalizacjach i otrzymuj powiadomienia, które pomogą Ci podjąć lepszą decyzję.</p>
            </div>
            <button type="button" className="price-alerts-settings">
              <Icon name="gear" /> Ustawienia alertów
            </button>
          </div>

          <section className="price-alerts-hero">
            <div className="price-alerts-hero-main">
              <span className="price-alerts-badge">Alert cenowy</span>
              <h2>
                {latestEventType
                  ? (hasPriceDrop ? 'Cena spadła!' : 'Cena wzrosła!')
                  : 'Brak nowych zmian cen'}
              </h2>
              <p>
                {activeListingEvent
                  ? `${activeListingEvent.offerType === 'RENT' ? 'Wynajem' : 'Kupno'}: ${activeListingEvent.title}`
                  : activeCityEvent
                    ? `${activeCityEvent.label} • ${activeCityEvent.propertyType} • ${activeCityEvent.marketType}`
                    : 'Dodaj oferty do ulubionych lub alerty lokalizacji, aby śledzić zmiany cen.'}
              </p>
              <small>
                <Icon name="pin" />
                {' '}
                {activeListingEvent
                  ? activeListingEvent.location
                  : activeCityEvent
                    ? [activeCityEvent.city, activeCityEvent.region].filter(Boolean).join(', ')
                    : 'Wybierz mieszkania lub lokalizacje, które Cię interesują'}
              </small>
              <div className="price-alerts-pricing">
                <strong>
                  {activeListingEvent
                    ? formatPln(activeListingEvent.currentPrice)
                    : activeCityEvent
                      ? formatM2(activeCityEvent.currentPerM2)
                      : '0 zł'}
                </strong>
                <em>
                  {activeListingEvent
                    ? formatPln(activeListingEvent.previousPrice)
                    : activeCityEvent
                      ? formatM2(activeCityEvent.previousPerM2)
                      : '0 zł'}
                </em>
                <span>
                  {latestEventType
                    ? `${latestDelta <= 0 ? 'Spadek' : 'Wzrost'} o ${activeListingEvent ? formatPln(Math.abs(latestDelta)) : formatM2(Math.abs(latestDelta))} (${latestDeltaPercent.toFixed(1).replace('.', ',')}%)`
                    : 'Brak porównań'}
                </span>
              </div>
              <div className="price-alerts-meta">
                {activeListingEvent ? (
                  <>
                    <span>Powierzchnia: <b>{activeListingEvent.area}</b></span>
                    <span>
                      Cena za m²: <b>
                        {formatM2(Math.round(activeListingEvent.currentPrice / Math.max(1, Number(activeListingEvent.area.replace(/[^\d,]/g, '').replace(',', '.')) || 1)))}
                      </b>
                    </span>
                  </>
                ) : (
                  <>
                    <span>Typ: <b>{activeCityEvent ? activeCityEvent.propertyType : '—'}</b></span>
                    <span>Rynek: <b>{activeCityEvent ? activeCityEvent.marketType : '—'}</b></span>
                  </>
                )}
              </div>
            </div>

            <div className="price-alerts-hero-image" style={{ backgroundImage: `url(${heroImage})` }} />

            <aside className="price-alerts-hero-side">
              <strong>Alert utworzony dla Ciebie</strong>
              <ul>
                <li>Ulubione oferty: <b>{priceEvents.length}</b></li>
                <li>Aktywne alerty lokalizacji: <b>{cityAlerts.length}</b></li>
                <li>Zdarzenia za m² (lokalizacje): <b>{cityPriceEvents.length}</b></li>
                <li>Ostatnia aktualizacja: <b>{latestEventType === 'listing' && latestEvent ? new Date(latestEvent.changedAt).toLocaleString('pl-PL') : latestEventType === 'city' && latestCityEvent ? new Date(latestCityEvent.changedAt).toLocaleString('pl-PL') : 'Brak'}</b></li>
                <li>Status: <b>{latestEventType ? 'Aktywny monitoring cen' : 'Czeka na pierwszą zmianę ceny'}</b></li>
              </ul>
              <button
                type="button"
                onClick={() => {
                  if (activeListingEvent) {
                    onOpenListing(activeListingEvent.listingId);
                    return;
                  }
                  navigate(ROUTES.priceHistory);
                }}
                disabled={!latestEventType}
              >
                {activeListingEvent ? 'Zobacz ofertę' : 'Zobacz historię cen'} <Icon name="arrow" />
              </button>
            </aside>
          </section>

          <section className="price-alerts-grid">
            <article className="price-alerts-card">
              <header>
                <h3>Średnia zmiana ceny za m² w alertach</h3>
                <p>{cityInsights.length > 0 ? `Na podstawie ${cityInsights.length} aktywnych lokalizacji` : 'Dodaj lokalizację, aby aktywować wykres'}</p>
              </header>
              <div className="price-alerts-kpi">
                <strong>{cityInsights.length > 0 ? formatM2(cityAveragePrice) : '0 zł/m²'}</strong>
                <span>{cityInsights.length > 0 ? `${cityAverageDelta >= 0 ? '+' : ''}${cityAverageDelta.toFixed(1).replace('.', ',')}%` : '0,0%'}</span>
              </div>
              <svg viewBox="0 0 320 130" aria-hidden="true" className="price-alerts-chart">
                <defs>
                  <linearGradient id="priceChartFill" x1="0" x2="0" y1="0" y2="1">
                    <stop offset="0%" stopColor="#26b36a" stopOpacity="0.34" />
                    <stop offset="100%" stopColor="#26b36a" stopOpacity="0" />
                  </linearGradient>
                </defs>
                <path d="M20 102 L58 92 L89 94 L116 85 L146 80 L175 81 L203 74 L234 67 L266 69 L300 44"
                  className="price-alerts-chart-line" />
                <path d="M20 102 L58 92 L89 94 L116 85 L146 80 L175 81 L203 74 L234 67 L266 69 L300 44 L300 120 L20 120 Z"
                  fill="url(#priceChartFill)" />
              </svg>
            </article>

            <article className="price-alerts-card">
              <header>
                <h3>Zmiana ceny za m² w dzielnicach</h3>
                <p>Ostatnie 12 miesięcy</p>
              </header>
              <div className="price-alerts-districts">
                {cityInsights.length > 0 ? cityInsights.slice(0, 5).map((item) => (
                  <div key={item.key}>
                    <strong>{item.district}</strong>
                    <span className="price-alerts-district-bar"><i style={{ width: `${item.barWidth}%` }} /></span>
                    <em>{formatM2(item.price)}</em>
                    <b>{item.delta >= 0 ? '+' : ''}{item.delta.toFixed(1).replace('.', ',')}%</b>
                  </div>
                )) : (
                  <div>
                    <strong>Brak danych</strong>
                    <span className="price-alerts-district-bar"><i style={{ width: '28%' }} /></span>
                    <em>0 zł/m²</em>
                    <b>0,0%</b>
                  </div>
                )}
              </div>
              <Link className="price-alerts-link" to={ROUTES.priceHistory}>Dodaj kolejne lokalizacje</Link>
            </article>

            <article className="price-alerts-card price-alerts-active-list">
              <header>
                <h3>Twoje alerty</h3>
              </header>
              <div className="price-alerts-active-items">
                {activeAlerts.length > 0 ? activeAlerts.map((item) => (
                  <button key={item.key} type="button" onClick={() => removeAlert(item.key)}>
                    <div>
                      <strong>{item.title}</strong>
                      <small>{item.meta}</small>
                    </div>
                    <span>Usuń</span>
                  </button>
                )) : (
                  <Link to={ROUTES.priceHistory}>
                    <div>
                      <strong>Brak aktywnych alertów</strong>
                      <small>Przejdź do historii cen i dodaj pierwszą lokalizację.</small>
                    </div>
                    <span>Dodaj</span>
                  </Link>
                )}
              </div>
              <Link className="price-alerts-manage" to={ROUTES.priceHistory}>Zarządzaj alertami</Link>
            </article>
          </section>
        </div>
      </div>
    </section>
  );
}

function AccountComparisonPage({ onOpenListing }: { onOpenListing: (id: number) => void }) {
  const navigate = useNavigate();
  type ComparisonPropertyFilter = 'ALL' | ApiPropertyType | 'LAND' | 'COMMERCIAL' | 'ROOM';

  const comparisonPropertyOptions: Array<{ value: ComparisonPropertyFilter; label: string }> = [
    { value: 'ALL', label: 'Wszystkie typy' },
    { value: 'APARTMENT', label: 'Mieszkanie' },
    { value: 'HOUSE', label: 'Dom' },
    { value: 'LAND', label: 'Działka' },
    { value: 'COMMERCIAL', label: 'Lokal użytkowy' },
    { value: 'ROOM', label: 'Pokój' },
  ];

  const [offerType, setOfferType] = useState<ApiOfferType>('SALE');
  const [propertyTypeFilter, setPropertyTypeFilter] = useState<ComparisonPropertyFilter>('ALL');
  const [search, setSearch] = useState('');
  const [pickerValue, setPickerValue] = useState<number | ''>('');
  const [selectedIds, setSelectedIds] = useState<number[]>([]);
  const [selectedDetails, setSelectedDetails] = useState<Record<number, ApiListingDetail>>({});
  const [valuesGridOffset, setValuesGridOffset] = useState(0);
  const summaryFirstMetricRef = useRef<HTMLElement | null>(null);
  const firstValueCellRef = useRef<HTMLDivElement | null>(null);
  const { items, loading } = useLiveListings(offerType);

  useEffect(() => {
    setSelectedIds([]);
    setPickerValue('');
    setSearch('');
    setPropertyTypeFilter('ALL');
  }, [offerType]);

  useEffect(() => {
    setSelectedIds((current) => current.filter((id) => items.some((item) => item.id === id)));
  }, [items]);

  useEffect(() => {
    if (selectedIds.length === 0) {
      // Zachowujemy referencje pustego obiektu - podstawienie nowego {} zmienialoby
      // zaleznosc selectedDetails i efekt wpadalby w nieskonczona petle renderow.
      setSelectedDetails((current) => (Object.keys(current).length === 0 ? current : {}));
      return;
    }

    const missing = selectedIds.filter((id) => !selectedDetails[id]);
    if (missing.length === 0) {
      return;
    }

    let cancelled = false;
    Promise.all(
      missing.map(async (id) => {
        try {
          const detail = await apiFetch<ApiListingDetail>(`/listings/${id}`);
          return { id, detail };
        } catch {
          return null;
        }
      }),
    ).then((resolved) => {
      if (cancelled) {
        return;
      }
      const entries = resolved.filter((item): item is { id: number; detail: ApiListingDetail } => item != null);
      if (entries.length === 0) {
        return;
      }
      setSelectedDetails((current) => {
        const next = { ...current };
        entries.forEach((entry) => {
          next[entry.id] = entry.detail;
        });
        return next;
      });
    });

    return () => {
      cancelled = true;
    };
  }, [selectedIds, selectedDetails]);

  const normalizedSearch = search.trim().toLowerCase();
  const searchable = useMemo(
    () => items
      .filter((item) => {
        if (!normalizedSearch) {
          return true;
        }
        const haystack = [item.title, item.city, item.district ?? '', item.address ?? ''].join(' ').toLowerCase();
        return haystack.includes(normalizedSearch);
      })
      .sort((left, right) => new Date(right.createdAt).getTime() - new Date(left.createdAt).getTime()),
    [items, normalizedSearch],
  );

  const selectedListings = useMemo(
    () => selectedIds.map((id) => items.find((item) => item.id === id)).filter((item): item is LiveListingSummary => Boolean(item)),
    [selectedIds, items],
  );

  const availableBySearch = useMemo(
    () => searchable.filter((item) => !selectedIds.includes(item.id)),
    [searchable, selectedIds],
  );

  const typeCounters = useMemo(() => {
    const apartment = availableBySearch.filter((item) => item.propertyType === 'APARTMENT').length;
    const house = availableBySearch.filter((item) => item.propertyType === 'HOUSE').length;
    return {
      ALL: availableBySearch.length,
      APARTMENT: apartment,
      HOUSE: house,
      LAND: 0,
      COMMERCIAL: 0,
      ROOM: 0,
    } satisfies Record<ComparisonPropertyFilter, number>;
  }, [availableBySearch]);

  const availableListings = useMemo(
    () => availableBySearch
      .filter((item) => {
        if (propertyTypeFilter === 'ALL') {
          return true;
        }
        if (propertyTypeFilter === 'APARTMENT' || propertyTypeFilter === 'HOUSE') {
          return item.propertyType === propertyTypeFilter;
        }
        return false;
      })
      .slice(0, 40),
    [availableBySearch, propertyTypeFilter],
  );

  useEffect(() => {
    if (availableListings.length === 0) {
      setPickerValue('');
      return;
    }
    if (pickerValue === '' || !availableListings.some((item) => item.id === pickerValue)) {
      setPickerValue(availableListings[0].id);
    }
  }, [availableListings, pickerValue]);

  const addSelectedListing = () => {
    if (pickerValue === '' || selectedIds.length >= 4) {
      return;
    }
    setSelectedIds((current) => (current.includes(pickerValue) ? current : [...current, pickerValue]));
  };

  const removeSelectedListing = (listingId: number) => {
    setSelectedIds((current) => current.filter((id) => id !== listingId));
  };

  useLayoutEffect(() => {
    const measureAlignmentOffset = () => {
      const summaryTop = summaryFirstMetricRef.current?.getBoundingClientRect().top;
      const valueTop = firstValueCellRef.current?.getBoundingClientRect().top;
      if (summaryTop == null || valueTop == null) {
        return;
      }
      const nextOffset = Math.max(0, valuesGridOffset + (summaryTop - valueTop));
      if (Math.abs(nextOffset - valuesGridOffset) < 0.5) {
        return;
      }
      setValuesGridOffset(nextOffset);
    };

    const frameId = window.requestAnimationFrame(measureAlignmentOffset);
    const resizeObserver = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(measureAlignmentOffset) : null;

    if (summaryFirstMetricRef.current) {
      resizeObserver?.observe(summaryFirstMetricRef.current);
    }
    if (firstValueCellRef.current) {
      resizeObserver?.observe(firstValueCellRef.current);
    }

    window.addEventListener('resize', measureAlignmentOffset);
    return () => {
      window.cancelAnimationFrame(frameId);
      resizeObserver?.disconnect();
      window.removeEventListener('resize', measureAlignmentOffset);
    };
  }, [valuesGridOffset, offerType, propertyTypeFilter, search, selectedIds.length, availableListings.length, selectedListings.length]);

  const pricePerSqm = (listing: LiveListingSummary) => {
    if (listing.area <= 0) {
      return null;
    }
    return listing.price / listing.area;
  };

  const bestIndices = (
    accessor: (listing: LiveListingSummary) => number | null,
    prefer: 'min' | 'max',
  ) => {
    const values = selectedListings
      .map((listing, index) => ({ index, value: accessor(listing) }))
      .filter((item): item is { index: number; value: number } => item.value != null && Number.isFinite(item.value));

    if (values.length < 2) {
      return new Set<number>();
    }

    const target = values.reduce((best, current) => {
      if (prefer === 'min') {
        return current.value < best ? current.value : best;
      }
      return current.value > best ? current.value : best;
    }, values[0].value);

    return new Set(values.filter((item) => item.value === target).map((item) => item.index));
  };

  const bestPrice = bestIndices((listing) => pricePerSqm(listing), 'min');
  const bestArea = bestIndices((listing) => listing.area, 'max');
  const bestRooms = bestIndices((listing) => listing.rooms, 'max');
  const newest = bestIndices((listing) => listing.yearBuilt ?? null, 'max');
  const totalComparisonColumns = 4;

  const metricRows = [
    { icon: 'coin', label: 'Cena', note: 'Cena całkowita' },
    { icon: 'calculator', label: 'Cena za m²', note: 'Przelicznik ceny do metrażu' },
    { icon: 'pin', label: 'Lokalizacja', note: 'Lokalizacja ogłoszenia' },
    { icon: 'building', label: 'Metraż', note: 'Powierzchnia użytkowa' },
    { icon: 'house', label: 'Liczba pokoi', note: 'Ilość pomieszczeń' },
    { icon: 'city', label: 'Piętro', note: 'Poziom mieszkania' },
    { icon: 'spark', label: 'Stan', note: 'Stan techniczny' },
    { icon: 'calendar', label: 'Rok budowy', note: 'Rok oddania do użytku' },
    { icon: 'coin', label: 'Czynsz', note: 'Miesięczny czynsz' },
    { icon: 'car', label: 'Parking', note: 'Miejsce parkingowe' },
    { icon: 'building', label: 'Balkon / Taras', note: 'Dodatkowa przestrzeń' },
    { icon: 'clock', label: 'Dostępność', note: 'Termin dostępności' },
  ] as const;

  const comparisonRows = [
    {
      label: 'Cena',
      values: selectedListings.map((listing) => `${formatPln(listing.price)}${offerType === 'RENT' ? ' / mies.' : ''}`),
      best: new Set<number>(),
    },
    {
      label: 'Cena za m²',
      values: selectedListings.map((listing) => {
        const perSqm = pricePerSqm(listing);
        return perSqm != null ? `${formatPln(Math.round(perSqm)).replace(' zł', '')} zł` : 'Brak danych';
      }),
      best: bestPrice,
    },
    {
      label: 'Lokalizacja',
      values: selectedListings.map((listing) => [listing.city, listing.district].filter(Boolean).join(', ')),
      best: new Set<number>(),
    },
    {
      label: 'Metraż',
      values: selectedListings.map((listing) => `${String(listing.area).replace('.', ',')} m²`),
      best: bestArea,
    },
    {
      label: 'Liczba pokoi',
      values: selectedListings.map((listing) => listingRoomsText(listing.rooms)),
      best: bestRooms,
    },
    {
      label: 'Piętro',
      values: selectedListings.map((listing) => listingFloorText(listing.floor, listing.buildingFloors) ?? 'Brak danych'),
      best: new Set<number>(),
    },
    {
      label: 'Stan',
      values: selectedListings.map((listing) => selectedDetails[listing.id]?.condition ?? listingMarketBadge(listing.market)),
      best: new Set<number>(),
    },
    {
      label: 'Rok budowy',
      values: selectedListings.map((listing) => (listing.yearBuilt ? String(listing.yearBuilt) : 'Brak danych')),
      best: newest,
    },
    {
      label: 'Czynsz',
      values: selectedListings.map((listing) => {
        if (offerType === 'SALE') {
          return '-';
        }
        const rentExtra = selectedDetails[listing.id]?.rentExtra;
        return rentExtra && rentExtra > 0 ? formatPln(rentExtra) : 'W cenie';
      }),
      best: new Set<number>(),
    },
    {
      label: 'Parking',
      values: selectedListings.map((listing) => {
        const amenities = selectedDetails[listing.id]?.amenities ?? [];
        if (amenities.includes('Garaż')) {
          return 'Garaż';
        }
        if (amenities.includes('Parking')) {
          return 'Miejsce parkingowe';
        }
        return 'Brak';
      }),
      best: new Set<number>(),
    },
    {
      label: 'Balkon / Taras',
      values: selectedListings.map((listing) => {
        const amenities = selectedDetails[listing.id]?.amenities ?? [];
        const hasBalcony = amenities.includes('Balkon');
        const hasTerrace = amenities.includes('Taras');
        if (hasBalcony && hasTerrace) {
          return 'Balkon i taras';
        }
        if (hasBalcony) {
          return 'Balkon';
        }
        if (hasTerrace) {
          return 'Taras';
        }
        return 'Brak';
      }),
      best: new Set<number>(),
    },
    {
      label: 'Dostępność',
      values: selectedListings.map((listing) => {
        if (offerType === 'RENT') {
          return selectedDetails[listing.id]?.availableFrom ?? 'Do uzgodnienia';
        }
        return 'Od zaraz';
      }),
      best: new Set<number>(),
    },
  ];

  const scoreCards = (() => {
    if (selectedListings.length < 2) {
      return selectedListings.map(() => null as number | null);
    }

    const normalize = (value: number, values: number[], prefer: 'min' | 'max') => {
      if (values.length < 2) {
        return null;
      }
      const min = Math.min(...values);
      const max = Math.max(...values);
      if (max === min) {
        return null;
      }
      const normalized = (value - min) / (max - min);
      return prefer === 'max' ? normalized : 1 - normalized;
    };

    const metricDefinitions: Array<{
      weight: number;
      prefer: 'min' | 'max';
      accessor: (listing: LiveListingSummary) => number | null;
      values: number[];
    }> = [
      {
        weight: 0.35,
        prefer: 'min',
        accessor: (listing) => pricePerSqm(listing),
        values: selectedListings.map((listing) => pricePerSqm(listing)).filter((value): value is number => value != null),
      },
      {
        weight: 0.25,
        prefer: 'max',
        accessor: (listing) => listing.area,
        values: selectedListings.map((listing) => listing.area),
      },
      {
        weight: 0.2,
        prefer: 'max',
        accessor: (listing) => listing.rooms,
        values: selectedListings.map((listing) => listing.rooms),
      },
      {
        weight: 0.2,
        prefer: 'max',
        accessor: (listing) => listing.yearBuilt,
        values: selectedListings.map((listing) => listing.yearBuilt).filter((value): value is number => value != null),
      },
    ];

    return selectedListings.map((listing) => {
      let weightedSum = 0;
      let activeWeight = 0;

      metricDefinitions.forEach((metric) => {
        const rawValue = metric.accessor(listing);
        if (rawValue == null) {
          return;
        }
        const normalized = normalize(rawValue, metric.values, metric.prefer);
        if (normalized == null) {
          return;
        }
        weightedSum += normalized * metric.weight;
        activeWeight += metric.weight;
      });

      if (activeWeight === 0) {
        return null;
      }

      const score = (weightedSum / activeWeight) * 10;
      return Math.round(score * 10) / 10;
    });
  })();

  return (
    <section className="comparison-page" aria-label="Porównanie mieszkań">
      <div className="comparison-layout">
        <AccountSidebar />

        <div className="comparison-main">
          <div className="comparison-breadcrumb">
            <Link to={ROUTES.home}>Strona główna</Link>
            <Icon name="chevron" />
            <Link className="active" to={ROUTES.comparison}>Porównywarka mieszkań</Link>
          </div>

          <div className="comparison-head">
            <div>
              <h1>Porównanie mieszkań</h1>
              <p>Porównaj do 4 mieszkań i wybierz najlepszą ofertę dla siebie.</p>
            </div>
            <div className="comparison-head-actions">
              <button type="button" className="ghost">Udostępnij porównanie</button>
              <button type="button" onClick={() => setSelectedIds([])} disabled={selectedIds.length === 0}>Wyczyść wszystko</button>
            </div>
          </div>

          <div className="comparison-workspace">
            <aside className="comparison-summary-column">
              <section className="comparison-controls" aria-label="Wybór ogłoszeń">
                <div className="comparison-mode-switch" role="tablist" aria-label="Tryb porównania">
                  <button
                    type="button"
                    role="tab"
                    aria-selected={offerType === 'SALE'}
                    className={offerType === 'SALE' ? 'active' : ''}
                    onClick={() => setOfferType('SALE')}
                  >
                    Sprzedaż
                  </button>
                  <button
                    type="button"
                    role="tab"
                    aria-selected={offerType === 'RENT'}
                    className={offerType === 'RENT' ? 'active' : ''}
                    onClick={() => setOfferType('RENT')}
                  >
                    Wynajem
                  </button>
                </div>

                <h3>Dodaj mieszkania do porównania</h3>
                <small>Możesz porównać do 4 ofert jednocześnie.</small>
                <div className="comparison-picker-row">
                  <input
                    type="search"
                    value={search}
                    placeholder="Szukaj mieszkań"
                    onChange={(event) => setSearch(event.target.value)}
                  />
                  <select
                    value={propertyTypeFilter}
                    onChange={(event) => setPropertyTypeFilter(event.target.value as ComparisonPropertyFilter)}
                  >
                    {comparisonPropertyOptions.map((option) => (
                      <option value={option.value} key={option.value}>
                        {option.label} ({typeCounters[option.value]})
                      </option>
                    ))}
                  </select>
                  <select
                    value={pickerValue === '' ? '' : String(pickerValue)}
                    onChange={(event) => setPickerValue(event.target.value ? Number(event.target.value) : '')}
                    disabled={availableListings.length === 0 || selectedIds.length >= 4}
                  >
                    {availableListings.length === 0 && <option value="">Brak dostępnych ogłoszeń</option>}
                    {availableListings.map((listing) => (
                      <option value={listing.id} key={listing.id}>
                        {listing.title} - {[listing.city, listing.district].filter(Boolean).join(', ')}
                      </option>
                    ))}
                  </select>
                  <button
                    type="button"
                    onClick={addSelectedListing}
                    disabled={pickerValue === '' || selectedIds.length >= 4 || availableListings.length === 0}
                  >
                    Dodaj
                  </button>
                </div>
              </section>

              <section className="comparison-summary-card">
                <h3>Podsumowanie porównania</h3>
                <div>
                  {metricRows.map((row, rowIndex) => (
                    <article key={row.label} ref={rowIndex === 0 ? summaryFirstMetricRef : undefined}>
                      <span><Icon name={row.icon} /></span>
                      <p>
                        <strong>{row.label}</strong>
                        <small>{row.note}</small>
                      </p>
                    </article>
                  ))}
                  <article>
                    <span><Icon name="star" /></span>
                    <p>
                      <strong>Ocena ogólna</strong>
                      <small>Na podstawie kluczowych czynników</small>
                    </p>
                  </article>
                </div>
              </section>
            </aside>

            <section className="comparison-content" aria-label="Tabela porównania">
              {loading ? (
                <section className="comparison-empty-state"><p>Wczytuję ogłoszenia...</p></section>
              ) : selectedListings.length === 0 ? (
                <section className="comparison-empty-state">
                  <h2>Brak wybranych ogłoszeń</h2>
                  <p>Dodaj pierwsze mieszkanie, aby rozpocząć porównanie.</p>
                </section>
              ) : (
                <>
                  <div className="comparison-cards-row" style={{ gridTemplateColumns: `repeat(${totalComparisonColumns}, minmax(210px, 1fr))` }}>
                    {selectedListings.map((listing) => (
                      <article className="comparison-offer-head" key={`head-${listing.id}`}>
                        <button type="button" className="comparison-remove" onClick={() => removeSelectedListing(listing.id)} aria-label="Usuń z porównania">×</button>
                        <div className="comparison-offer-photo" style={{ backgroundImage: `url(${listing.coverPhoto ?? heroImage})` }} />
                        <strong>{listing.title}</strong>
                        <small>{[listing.city, listing.district].filter(Boolean).join(', ')}</small>
                        <em>{formatPln(listing.price)}{offerType === 'RENT' ? ' / mies.' : ''}</em>
                        <button type="button" onClick={() => onOpenListing(listing.id)}>Zobacz ogłoszenie</button>
                      </article>
                    ))}

                    {selectedListings.length < totalComparisonColumns && (
                      <article className="comparison-add-tile" role="button" tabIndex={0} onClick={addSelectedListing} onKeyDown={(event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); addSelectedListing(); } }}>
                        <span><Icon name="plus" /></span>
                        <strong>Dodaj mieszkanie</strong>
                        <small>Wybierz kolejną ofertę (max. 4)</small>
                      </article>
                    )}

                    {Array.from({ length: Math.max(0, totalComparisonColumns - selectedListings.length - (selectedListings.length < totalComparisonColumns ? 1 : 0)) }, (_, index) => (
                      <article className="comparison-card-empty" key={`card-empty-${index}`} aria-hidden="true" />
                    ))}
                  </div>

                  <div className="comparison-values-grid" style={{ gridTemplateColumns: `repeat(${totalComparisonColumns}, minmax(210px, 1fr))`, paddingTop: valuesGridOffset }}>
                    {comparisonRows.map((row, rowIndex) => (
                      <Fragment key={row.label}>
                        {Array.from({ length: totalComparisonColumns }, (_, index) => {
                          const listing = selectedListings[index];
                          const value = row.values[index];
                          if (!listing || value == null) {
                            return <div className="comparison-cell comparison-cell-empty" ref={rowIndex === 0 && index === 0 ? firstValueCellRef : undefined} key={`${row.label}-empty-${index}`} />;
                          }
                          return (
                            <div className="comparison-cell" ref={rowIndex === 0 && index === 0 ? firstValueCellRef : undefined} key={`${row.label}-${listing.id}`}>
                              <span>{value}</span>
                              {row.best.has(index) && <em>Najlepsza</em>}
                            </div>
                          );
                        })}
                      </Fragment>
                    ))}

                    {Array.from({ length: totalComparisonColumns }, (_, index) => {
                      const listing = selectedListings[index];
                      const score = scoreCards[index];
                      if (!listing || score == null) {
                        return (
                          <div className="comparison-cell comparison-rating comparison-cell-empty" key={`score-empty-${index}`}>
                            {listing && <small>Dodaj min. 2 oferty do porównania</small>}
                          </div>
                        );
                      }
                      return (
                        <div className="comparison-cell comparison-rating" key={`score-${listing.id}`}>
                          <strong>{score.toFixed(1).replace('.', ',')} / 10</strong>
                          <small>{score >= 8.5 ? 'Świetna' : score >= 7 ? 'Bardzo dobra' : score >= 5.5 ? 'Dobra' : 'Do poprawy'}</small>
                        </div>
                      );
                    })}
                  </div>
                </>
              )}
            </section>
          </div>
        </div>
      </div>
    </section>
  );
}

function AccountMeetingsPage() {
  const { notify } = useNotifications();
  type MeetingType = 'buy' | 'rent' | 'own';
  type MeetingStatus = 'planned' | 'completed' | 'cancelled';
  type MeetingItem = {
    id: string;
    title: string;
    subtitle: string;
    location: string;
    dateKey: string;
    start: string;
    end: string;
    type: MeetingType;
    status: MeetingStatus;
  };

  const formatDateKey = (value: Date) => {
    const year = value.getFullYear();
    const month = String(value.getMonth() + 1).padStart(2, '0');
    const day = String(value.getDate()).padStart(2, '0');
    return `${year}-${month}-${day}`;
  };

  const storageKey = 'polskalokalnie-account-meetings';
  const [calendarMonth, setCalendarMonth] = useState(() => new Date(new Date().getFullYear(), new Date().getMonth(), 1));
  const [selectedDateKey, setSelectedDateKey] = useState(() => formatDateKey(new Date()));
  const [showBuyMeetings, setShowBuyMeetings] = useState(true);
  const [showRentMeetings, setShowRentMeetings] = useState(true);
  const [showOwnMeetings, setShowOwnMeetings] = useState(true);
  const [isCreateFormOpen, setIsCreateFormOpen] = useState(false);
  const [editingMeetingId, setEditingMeetingId] = useState<string | null>(null);
  const [formError, setFormError] = useState('');
  const [newMeeting, setNewMeeting] = useState({
    title: '',
    subtitle: '',
    location: '',
    dateKey: formatDateKey(new Date()),
    start: '10:00',
    end: '10:30',
    type: 'buy' as MeetingType,
  });
  const defaultMeetings = useMemo<MeetingItem[]>(() => {
    const now = new Date();
    const addDays = (days: number) => {
      const date = new Date(now);
      date.setDate(date.getDate() + days);
      return formatDateKey(date);
    };
    const addMonths = (months: number) => {
      const date = new Date(now);
      date.setMonth(date.getMonth() + months);
      return formatDateKey(date);
    };

    return [
      {
        id: 'meeting-1',
        title: 'Spotkanie z kupującym',
        subtitle: 'Mieszkanie 3-pokojowe, 62 m²',
        location: 'ul. Mokotowska 45, Warszawa',
        dateKey: addDays(0),
        start: '10:00',
        end: '10:30',
        type: 'buy',
        status: 'planned',
      },
      {
        id: 'meeting-2',
        title: 'Spotkanie z wynajmującym',
        subtitle: 'Mieszkanie 2-pokojowe, 45 m²',
        location: 'ul. Puławska 10, Warszawa',
        dateKey: addDays(0),
        start: '12:00',
        end: '12:30',
        type: 'rent',
        status: 'planned',
      },
      {
        id: 'meeting-3',
        title: 'Spotkanie własne',
        subtitle: 'Konsultacja z doradcą',
        location: 'Online',
        dateKey: addDays(0),
        start: '15:00',
        end: '15:30',
        type: 'own',
        status: 'planned',
      },
      {
        id: 'meeting-4',
        title: 'Spotkanie z kupującym',
        subtitle: 'Mieszkanie 4-pokojowe, 78 m²',
        location: 'ul. Dobra 12, Warszawa',
        dateKey: addDays(1),
        start: '17:30',
        end: '18:00',
        type: 'buy',
        status: 'planned',
      },
      {
        id: 'meeting-test-now-1',
        title: 'Test: szybka konsultacja',
        subtitle: 'Spotkanie testowe na dziś',
        location: 'Biuro Mieszko, Warszawa',
        dateKey: addDays(0),
        start: '18:30',
        end: '19:00',
        type: 'own',
        status: 'planned',
      },
      {
        id: 'meeting-test-week-ago-1',
        title: 'Test: historia sprzed tygodnia',
        subtitle: 'Weryfikacja widoku archiwum',
        location: 'Online',
        dateKey: addDays(-7),
        start: '11:00',
        end: '11:30',
        type: 'rent',
        status: 'completed',
      },
      {
        id: 'meeting-test-prev-month-1',
        title: 'Test: poprzedni miesiąc',
        subtitle: 'Spotkanie archiwalne',
        location: 'ul. Grzybowska 22, Warszawa',
        dateKey: addMonths(-1),
        start: '09:30',
        end: '10:00',
        type: 'buy',
        status: 'completed',
      },
    ];
  }, []);
  const [meetings, setMeetings] = useState<MeetingItem[]>(() => {
    try {
      const raw = window.localStorage.getItem(storageKey);
      if (!raw) {
        return defaultMeetings;
      }
      const parsed = JSON.parse(raw);
      if (!Array.isArray(parsed)) {
        return defaultMeetings;
      }
      const normalized = parsed
        .filter((item): item is MeetingItem => (
          item
          && typeof item.id === 'string'
          && typeof item.title === 'string'
          && typeof item.subtitle === 'string'
          && typeof item.location === 'string'
          && typeof item.dateKey === 'string'
          && typeof item.start === 'string'
          && typeof item.end === 'string'
          && (item.type === 'buy' || item.type === 'rent' || item.type === 'own')
          && (item.status === 'planned' || item.status === 'completed' || item.status === 'cancelled')
        ))
        .sort((left, right) => `${left.dateKey}-${left.start}`.localeCompare(`${right.dateKey}-${right.start}`));
      return normalized.length > 0 ? normalized : defaultMeetings;
    } catch {
      return defaultMeetings;
    }
  });

  useEffect(() => {
    window.localStorage.setItem(storageKey, JSON.stringify(meetings));
  }, [meetings]);

  useEffect(() => {
    const requiredTestIds = ['meeting-test-now-1', 'meeting-test-week-ago-1', 'meeting-test-prev-month-1'];
    setMeetings((current) => {
      const existingIds = new Set(current.map((meeting) => meeting.id));
      const missing = defaultMeetings.filter((meeting) => requiredTestIds.includes(meeting.id) && !existingIds.has(meeting.id));
      if (missing.length === 0) {
        return current;
      }
      return [...current, ...missing].sort((left, right) => `${left.dateKey}-${left.start}`.localeCompare(`${right.dateKey}-${right.start}`));
    });
  }, [defaultMeetings]);

  const monthLabel = calendarMonth.toLocaleDateString('pl-PL', { month: 'long', year: 'numeric' });
  const todayKey = formatDateKey(new Date());
  const isPastDateKey = (dateKey: string) => dateKey < todayKey;
  const selectedDateIsPast = isPastDateKey(selectedDateKey);
  const firstMonthDay = new Date(calendarMonth.getFullYear(), calendarMonth.getMonth(), 1);
  const firstMonthWeekday = (firstMonthDay.getDay() + 6) % 7;
  const calendarDays = Array.from({ length: 42 }, (_, index) => {
    const dayOffset = index - firstMonthWeekday;
    return new Date(calendarMonth.getFullYear(), calendarMonth.getMonth(), dayOffset + 1);
  });

  const visibleSelectedMeetings = meetings
    .filter((meeting) => meeting.dateKey === selectedDateKey)
    .filter((meeting) => (
      (meeting.type === 'buy' && showBuyMeetings)
      || (meeting.type === 'rent' && showRentMeetings)
      || (meeting.type === 'own' && showOwnMeetings)
    ))
    .sort((left, right) => left.start.localeCompare(right.start));

  const selectedDayTotals = {
    all: meetings.filter((meeting) => meeting.dateKey === selectedDateKey).length,
    planned: meetings.filter((meeting) => meeting.dateKey === selectedDateKey && meeting.status === 'planned').length,
    completed: meetings.filter((meeting) => meeting.dateKey === selectedDateKey && meeting.status === 'completed').length,
  };

  const selectedDateLabel = (() => {
    const selectedDate = new Date(selectedDateKey);
    return selectedDate.toLocaleDateString('pl-PL', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' });
  })();

  const moveMonth = (delta: number) => {
    setCalendarMonth((current) => new Date(current.getFullYear(), current.getMonth() + delta, 1));
  };

  const resetFormForDate = (dateKey: string) => {
    setNewMeeting({
      title: '',
      subtitle: '',
      location: '',
      dateKey,
      start: '10:00',
      end: '10:30',
      type: 'buy',
    });
    setEditingMeetingId(null);
    setFormError('');
  };

  const openCreateForm = () => {
    if (selectedDateIsPast) {
      setFormError('Dla dni archiwalnych możesz tylko przeglądać historię spotkań.');
      return;
    }
    const nextOpen = !isCreateFormOpen;
    setIsCreateFormOpen(nextOpen);
    if (nextOpen) {
      resetFormForDate(selectedDateKey);
    }
  };

  const toMinutes = (timeValue: string) => {
    const [hours, minutes] = timeValue.split(':').map((value) => Number(value));
    return (hours * 60) + minutes;
  };

  const hasTimeConflict = (candidateDateKey: string, candidateStart: string, candidateEnd: string, ignoreId?: string | null) => {
    const candidateStartMinutes = toMinutes(candidateStart);
    const candidateEndMinutes = toMinutes(candidateEnd);
    return meetings.some((meeting) => {
      if (meeting.dateKey !== candidateDateKey || meeting.id === ignoreId) {
        return false;
      }
      const startMinutes = toMinutes(meeting.start);
      const endMinutes = toMinutes(meeting.end);
      return candidateStartMinutes < endMinutes && candidateEndMinutes > startMinutes;
    });
  };

  const setStatus = (id: string, status: MeetingStatus) => {
    if (selectedDateIsPast) {
      return;
    }
    const target = meetings.find((meeting) => meeting.id === id);
    setMeetings((current) => current.map((meeting) => (meeting.id === id ? { ...meeting, status } : meeting)));
    if (target && status === 'cancelled') {
      notify({ category: 'system', icon: 'calendar', title: 'Spotkanie odwołane', body: `Spotkanie „${target.title}” zostało odwołane.`, link: 'meetings', dedupeKey: `meeting-${id}-cancelled` });
    } else if (target && status === 'completed') {
      notify({ category: 'system', icon: 'check', title: 'Spotkanie potwierdzone', body: `Spotkanie „${target.title}” zostało oznaczone jako zrealizowane.`, link: 'meetings', dedupeKey: `meeting-${id}-completed` });
    }
  };

  const removeMeeting = (id: string) => {
    if (selectedDateIsPast) {
      return;
    }
    setMeetings((current) => current.filter((meeting) => meeting.id !== id));
    if (editingMeetingId === id) {
      setEditingMeetingId(null);
      setIsCreateFormOpen(false);
    }
  };

  const openEditMeeting = (meeting: MeetingItem) => {
    if (isPastDateKey(meeting.dateKey)) {
      return;
    }
    setEditingMeetingId(meeting.id);
    setNewMeeting({
      title: meeting.title,
      subtitle: meeting.subtitle,
      location: meeting.location,
      dateKey: meeting.dateKey,
      start: meeting.start,
      end: meeting.end,
      type: meeting.type,
    });
    setFormError('');
    setSelectedDateKey(meeting.dateKey);
    const selectedDate = new Date(meeting.dateKey);
    setCalendarMonth(new Date(selectedDate.getFullYear(), selectedDate.getMonth(), 1));
    setIsCreateFormOpen(true);
  };

  const addMeeting = () => {
    if (isPastDateKey(newMeeting.dateKey)) {
      setFormError('Nie można dodawać ani edytować spotkań wstecz.');
      return;
    }
    if (!newMeeting.title.trim()) {
      setFormError('Podaj nazwę spotkania.');
      return;
    }
    if (!newMeeting.start || !newMeeting.end || newMeeting.end <= newMeeting.start) {
      setFormError('Ustaw poprawny zakres godzin (koniec po starcie).');
      return;
    }
    if (hasTimeConflict(newMeeting.dateKey, newMeeting.start, newMeeting.end, editingMeetingId)) {
      setFormError('Masz już inne spotkanie w tym przedziale czasu.');
      return;
    }

    if (editingMeetingId) {
      setMeetings((current) => current.map((meeting) => (meeting.id === editingMeetingId
        ? {
          ...meeting,
          title: newMeeting.title.trim(),
          subtitle: newMeeting.subtitle.trim() || 'Spotkanie ręcznie dodane',
          location: newMeeting.location.trim() || 'Do uzupełnienia',
          dateKey: newMeeting.dateKey,
          start: newMeeting.start,
          end: newMeeting.end,
          type: newMeeting.type,
        }
        : meeting)));
    } else {
      setMeetings((current) => [
        ...current,
        {
          id: `meeting-${Date.now()}`,
          title: newMeeting.title.trim(),
          subtitle: newMeeting.subtitle.trim() || 'Spotkanie ręcznie dodane',
          location: newMeeting.location.trim() || 'Do uzupełnienia',
          dateKey: newMeeting.dateKey,
          start: newMeeting.start,
          end: newMeeting.end,
          type: newMeeting.type,
          status: 'planned',
        },
      ]);
    }

    if (!editingMeetingId) {
      notify({ category: 'system', icon: 'calendar', title: 'Spotkanie zaplanowane', body: `„${newMeeting.title.trim()}” — ${newMeeting.dateKey}, godz. ${newMeeting.start}.`, link: 'meetings' });
    }

    setFormError('');
    setSelectedDateKey(newMeeting.dateKey);
    const selectedDate = new Date(newMeeting.dateKey);
    setCalendarMonth(new Date(selectedDate.getFullYear(), selectedDate.getMonth(), 1));
    resetFormForDate(newMeeting.dateKey);
    setIsCreateFormOpen(false);
  };

  const markerClass = (type: MeetingType) => {
    if (type === 'buy') {
      return 'buy';
    }
    if (type === 'rent') {
      return 'rent';
    }
    return 'own';
  };

  const statusLabel = (status: MeetingStatus) => {
    if (status === 'completed') {
      return 'Zrealizowane';
    }
    if (status === 'cancelled') {
      return 'Odwołane';
    }
    return 'Zaplanowane';
  };

  useEffect(() => {
    if (selectedDateIsPast) {
      setIsCreateFormOpen(false);
      setEditingMeetingId(null);
      setFormError('Dla dni archiwalnych możesz tylko przeglądać historię spotkań.');
    } else if (formError === 'Dla dni archiwalnych możesz tylko przeglądać historię spotkań.') {
      setFormError('');
    }
  }, [selectedDateIsPast]);

  return (
    <section className="account-settings-page meetings-page" aria-label="Umówione spotkania">
      <div className="account-layout">
        <AccountSidebar />

        <div className="account-main account-settings-main meetings-main">
          <header className="settings-head meetings-head">
            <h1>Umówione spotkania</h1>
            <p>Zarządzaj swoimi spotkaniami i przeglądaj nadchodzące terminy.</p>
          </header>

          <div className="meetings-grid">
            <aside className="meetings-calendar-card">
              <div className="meetings-month-nav">
                <button type="button" onClick={() => moveMonth(-1)} aria-label="Poprzedni miesiąc"><Icon name="chevron" /></button>
                <strong>{monthLabel}</strong>
                <button type="button" onClick={() => moveMonth(1)} aria-label="Następny miesiąc"><Icon name="chevron" /></button>
              </div>

              <button
                type="button"
                className="meetings-today-button"
                onClick={() => {
                  const today = new Date();
                  const todayKey = formatDateKey(today);
                  setSelectedDateKey(todayKey);
                  setCalendarMonth(new Date(today.getFullYear(), today.getMonth(), 1));
                }}
              >
                Przejdź do dziś
              </button>

              <div className="meetings-weekdays">
                {['Pn', 'Wt', 'Śr', 'Cz', 'Pt', 'So', 'N'].map((day) => <span key={day}>{day}</span>)}
              </div>

              <div className="meetings-days-grid">
                {calendarDays.map((date) => {
                  const key = formatDateKey(date);
                  const isOutside = date.getMonth() !== calendarMonth.getMonth();
                  const isActive = key === selectedDateKey;
                  const isPast = isPastDateKey(key);
                  const hasMeeting = meetings.some((meeting) => meeting.dateKey === key);

                  return (
                    <button
                      type="button"
                      key={key}
                      className={`${isOutside ? 'outside' : ''} ${isActive ? 'active' : ''} ${isPast ? 'past' : ''}`.trim()}
                      onClick={() => {
                        setSelectedDateKey(key);
                        setFormError('');
                        setCalendarMonth(new Date(date.getFullYear(), date.getMonth(), 1));
                      }}
                    >
                      <span>{date.getDate()}</span>
                      {hasMeeting && <i />}
                    </button>
                  );
                })}
              </div>

              <button type="button" className="meetings-add-button" onClick={openCreateForm} disabled={selectedDateIsPast}>
                <Icon name="plus" /> Dodaj spotkanie
              </button>

              <section className="meetings-filters-card">
                <h3>Filtry</h3>
                <label><input type="checkbox" checked={showBuyMeetings && showRentMeetings && showOwnMeetings} onChange={(event) => {
                  setShowBuyMeetings(event.target.checked);
                  setShowRentMeetings(event.target.checked);
                  setShowOwnMeetings(event.target.checked);
                }} /> Pokaż wszystkie</label>
                <label><input type="checkbox" checked={showBuyMeetings} onChange={(event) => setShowBuyMeetings(event.target.checked)} /> Spotkania z kupującymi</label>
                <label><input type="checkbox" checked={showRentMeetings} onChange={(event) => setShowRentMeetings(event.target.checked)} /> Spotkania z wynajmującymi</label>
                <label><input type="checkbox" checked={showOwnMeetings} onChange={(event) => setShowOwnMeetings(event.target.checked)} /> Spotkania własne</label>
              </section>
            </aside>

            <section className="meetings-list-card">
              <header>
                <h2>{selectedDateLabel}</h2>
                <div className="meetings-day-summary">
                  <span>Wszystkie: {selectedDayTotals.all}</span>
                  <span>Zaplanowane: {selectedDayTotals.planned}</span>
                  <span>Zrealizowane: {selectedDayTotals.completed}</span>
                </div>
                {selectedDateIsPast && (
                  <p className="meetings-history-note">Dzień archiwalny: możesz wyłącznie przeglądać historię spotkań.</p>
                )}
              </header>

              <div className="meetings-list">
                {visibleSelectedMeetings.length === 0 && <p className="meetings-empty">Brak spotkań dla wybranego dnia.</p>}
                {visibleSelectedMeetings.map((meeting) => (
                  <article key={meeting.id}>
                    <div className="time">{meeting.start}<small>- {meeting.end}</small></div>
                    <div className="info">
                      <strong><i className={markerClass(meeting.type)} />{meeting.title}</strong>
                      <p>{meeting.subtitle}</p>
                      <small>{meeting.location}</small>
                    </div>
                    <div className="meeting-side-actions">
                      <em className={`status-${meeting.status}`}>{statusLabel(meeting.status)}</em>
                      {!selectedDateIsPast && (
                        <div className="meeting-row-actions">
                          {meeting.status !== 'completed' && (
                            <button type="button" onClick={() => setStatus(meeting.id, 'completed')} aria-label="Oznacz jako zrealizowane"><Icon name="check" /></button>
                          )}
                          {meeting.status !== 'planned' && (
                            <button type="button" onClick={() => setStatus(meeting.id, 'planned')} aria-label="Oznacz jako zaplanowane"><Icon name="clock" /></button>
                          )}
                          <button type="button" onClick={() => setStatus(meeting.id, 'cancelled')} aria-label="Oznacz jako odwołane"><Icon name="warning" /></button>
                          <button type="button" onClick={() => openEditMeeting(meeting)} aria-label="Edytuj spotkanie"><Icon name="gear" /></button>
                          <button type="button" onClick={() => removeMeeting(meeting.id)} aria-label="Usuń spotkanie"><Icon name="trash" /></button>
                        </div>
                      )}
                    </div>
                  </article>
                ))}
              </div>

              {isCreateFormOpen && (
                <div className="meetings-create-form">
                  <h3>{editingMeetingId ? 'Edytuj spotkanie' : 'Dodaj spotkanie ręcznie'}</h3>
                  <div className="fields">
                    <label>Nazwa spotkania<input type="text" value={newMeeting.title} onChange={(event) => setNewMeeting((current) => ({ ...current, title: event.target.value }))} placeholder="np. Spotkanie z kupującym" /></label>
                    <label>Opis<input type="text" value={newMeeting.subtitle} onChange={(event) => setNewMeeting((current) => ({ ...current, subtitle: event.target.value }))} placeholder="np. Mieszkanie 3-pokojowe, 62 m²" /></label>
                    <label>Miejsce<input type="text" value={newMeeting.location} onChange={(event) => setNewMeeting((current) => ({ ...current, location: event.target.value }))} placeholder="np. ul. Mokotowska 45, Warszawa" /></label>
                    <label>Data<input type="date" min={todayKey} value={newMeeting.dateKey} onChange={(event) => setNewMeeting((current) => ({ ...current, dateKey: event.target.value }))} /></label>
                    <label>Typ
                      <select value={newMeeting.type} onChange={(event) => setNewMeeting((current) => ({ ...current, type: event.target.value as MeetingType }))}>
                        <option value="buy">Spotkanie z kupującym</option>
                        <option value="rent">Spotkanie z wynajmującym</option>
                        <option value="own">Spotkanie własne</option>
                      </select>
                    </label>
                    <label>Godzina od<input type="time" value={newMeeting.start} onChange={(event) => setNewMeeting((current) => ({ ...current, start: event.target.value }))} /></label>
                    <label>Godzina do<input type="time" value={newMeeting.end} onChange={(event) => setNewMeeting((current) => ({ ...current, end: event.target.value }))} /></label>
                  </div>
                  {formError && <p className="meetings-error">{formError}</p>}
                  <div className="actions">
                    <button
                      type="button"
                      className="secondary"
                      onClick={() => {
                        setIsCreateFormOpen(false);
                        resetFormForDate(selectedDateKey);
                      }}
                    >
                      Anuluj
                    </button>
                    <button type="button" className="primary" onClick={addMeeting}>{editingMeetingId ? 'Zapisz zmiany' : 'Zapisz spotkanie'}</button>
                  </div>
                </div>
              )}
            </section>
          </div>
        </div>
      </div>
    </section>
  );
}

function MyApiListingsBand({ onOpenListing }: { onOpenListing: (id: number) => void }) {
  const [items, setItems] = useState<ApiListingSummary[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    let cancelled = false;
    apiFetch<ApiListingSummary[]>('/listings/mine')
      .then((data) => { if (!cancelled) { setItems(data); setLoading(false); } })
      .catch((err) => { if (!cancelled) { setError(err instanceof Error ? err.message : 'Nie udało się wczytać Twoich ogłoszeń.'); setLoading(false); } });
    return () => { cancelled = true; };
  }, []);

  if (loading) {
    return <section className="listings-api-band"><p className="listings-api-note">Wczytywanie Twoich ogłoszeń…</p></section>;
  }
  if (error) {
    return <section className="listings-api-band"><p className="listings-api-note">{error}</p></section>;
  }
  if (items.length === 0) {
    return null;
  }

  return (
    <section className="listings-api-band" aria-label="Twoje ogłoszenia w serwisie">
      <header>
        <h2>Twoje ogłoszenia w serwisie</h2>
        <span>{items.length}</span>
      </header>
      <div className="listings-api-grid">
        {items.map((item) => (
          <button type="button" key={item.id} className="listings-api-card" onClick={() => onOpenListing(item.id)}>
            <div className="listings-api-thumb" style={{ backgroundImage: `url(${item.coverPhoto ?? heroImage})` }}>
              <span className={`ld-status ld-status-${item.status.toLowerCase()}`}>{LISTING_STATUS_LABELS[item.status]}</span>
            </div>
            <div className="listings-api-body">
              <strong>{item.title}</strong>
              <small>{[item.city, item.district].filter(Boolean).join(', ')}</small>
              <em>{formatPln(item.price)}{item.offerType === 'RENT' ? ' /mc' : ''}</em>
              <span>{`${item.area}`.replace('.', ',')} m² • {listingRoomsText(item.rooms)}</span>
            </div>
          </button>
        ))}
      </div>
    </section>
  );
}

function AccountListingsPage({ onOpenListing }: { onOpenListing: (id: number) => void }) {
  const navigate = useNavigate();
  type ListingCategory = 'sale' | 'rent';
  type ListingStatus = 'active' | 'paused' | 'archived';
  type ListingItem = {
    id: string;
    title: string;
    address: string;
    area: number;
    rooms: number;
    floor: number;
    price: number;
    monthlyFees: number;
    views: number;
    saves: number;
    addedDate: string;
    image: string;
    imagePosition: string;
    category: ListingCategory;
    status: ListingStatus;
    pausedUntil?: string | null;
    promoted: boolean;
    isApi?: boolean;
    apiId?: number;
    apiStatus?: ApiListingStatus;
  };

  type ListingTab = 'all' | 'sale' | 'rent' | 'archived';

  const listingDefaults: ListingItem[] = [
    {
      id: 'listing-1',
      title: 'Mieszkanie 3-pokojowe, 62 m²',
      address: 'ul. Mokotowska 45, Warszawa',
      area: 62,
      rooms: 3,
      floor: 4,
      price: 850000,
      monthlyFees: 0,
      views: 245,
      saves: 18,
      addedDate: '2025-05-10',
      image: heroImage,
      imagePosition: '47% 58%',
      category: 'sale',
      status: 'active',
      pausedUntil: null,
      promoted: false,
    },
    {
      id: 'listing-2',
      title: 'Mieszkanie 2-pokojowe, 45 m²',
      address: 'ul. Puławska 10, Warszawa',
      area: 45,
      rooms: 2,
      floor: 2,
      price: 650000,
      monthlyFees: 0,
      views: 312,
      saves: 25,
      addedDate: '2025-05-02',
      image: cityImage,
      imagePosition: '52% 55%',
      category: 'sale',
      status: 'active',
      pausedUntil: null,
      promoted: true,
    },
    {
      id: 'listing-3',
      title: 'Mieszkanie 4-pokojowe, 78 m²',
      address: 'ul. Dobra 12, Warszawa',
      area: 78,
      rooms: 4,
      floor: 5,
      price: 1120000,
      monthlyFees: 0,
      views: 189,
      saves: 12,
      addedDate: '2025-04-28',
      image: loginRoomImage,
      imagePosition: '60% 52%',
      category: 'sale',
      status: 'active',
      pausedUntil: null,
      promoted: false,
    },
    {
      id: 'listing-4',
      title: 'Mieszkanie 2-pokojowe, 50 m²',
      address: 'ul. Krucza 5, Warszawa',
      area: 50,
      rooms: 2,
      floor: 3,
      price: 2800,
      monthlyFees: 600,
      views: 156,
      saves: 7,
      addedDate: '2025-04-15',
      image: heroImage,
      imagePosition: '64% 60%',
      category: 'rent',
      status: 'active',
      pausedUntil: null,
      promoted: false,
    },
    {
      id: 'listing-5',
      title: 'Kawalerka, 28 m²',
      address: 'ul. Złota 65, Warszawa',
      area: 28,
      rooms: 1,
      floor: 10,
      price: 2200,
      monthlyFees: 500,
      views: 98,
      saves: 3,
      addedDate: '2025-04-05',
      image: cityImage,
      imagePosition: '56% 48%',
      category: 'rent',
      status: 'paused',
      pausedUntil: null,
      promoted: false,
    },
    {
      id: 'listing-test-1',
      title: 'Test: Apartament 3-pokojowy, 71 m²',
      address: 'ul. Grzybowska 80, Warszawa',
      area: 71,
      rooms: 3,
      floor: 6,
      price: 979000,
      monthlyFees: 0,
      views: 421,
      saves: 44,
      addedDate: '2025-06-12',
      image: heroImage,
      imagePosition: '54% 52%',
      category: 'sale',
      status: 'active',
      pausedUntil: null,
      promoted: true,
    },
    {
      id: 'listing-test-2',
      title: 'Test: Mieszkanie 2-pokojowe, 39 m²',
      address: 'ul. Płocka 16, Warszawa',
      area: 39,
      rooms: 2,
      floor: 1,
      price: 3100,
      monthlyFees: 450,
      views: 178,
      saves: 16,
      addedDate: '2025-06-03',
      image: cityImage,
      imagePosition: '49% 56%',
      category: 'rent',
      status: 'active',
      pausedUntil: null,
      promoted: false,
    },
    {
      id: 'listing-test-3',
      title: 'Test: Loft 1-pokojowy, 33 m²',
      address: 'ul. Targowa 24, Warszawa',
      area: 33,
      rooms: 1,
      floor: 4,
      price: 2600,
      monthlyFees: 390,
      views: 96,
      saves: 9,
      addedDate: '2025-05-21',
      image: loginRoomImage,
      imagePosition: '63% 50%',
      category: 'rent',
      status: 'archived',
      pausedUntil: null,
      promoted: false,
    },
    {
      id: 'listing-test-4',
      title: 'Test: Penthouse 5-pokojowy, 122 m²',
      address: 'ul. Sienna 39, Warszawa',
      area: 122,
      rooms: 5,
      floor: 11,
      price: 1990000,
      monthlyFees: 0,
      views: 538,
      saves: 61,
      addedDate: '2025-06-22',
      image: heroImage,
      imagePosition: '58% 48%',
      category: 'sale',
      status: 'active',
      pausedUntil: null,
      promoted: true,
    },
    {
      id: 'listing-test-5',
      title: 'Test: Mieszkanie 3-pokojowe, 67 m²',
      address: 'ul. Jana Kazimierza 31, Warszawa',
      area: 67,
      rooms: 3,
      floor: 7,
      price: 905000,
      monthlyFees: 0,
      views: 287,
      saves: 29,
      addedDate: '2025-06-18',
      image: cityImage,
      imagePosition: '50% 55%',
      category: 'sale',
      status: 'active',
      pausedUntil: null,
      promoted: false,
    },
    {
      id: 'listing-test-6',
      title: 'Test: Mieszkanie 2-pokojowe, 41 m²',
      address: 'ul. Chłodna 22, Warszawa',
      area: 41,
      rooms: 2,
      floor: 5,
      price: 3200,
      monthlyFees: 520,
      views: 233,
      saves: 21,
      addedDate: '2025-06-15',
      image: loginRoomImage,
      imagePosition: '62% 52%',
      category: 'rent',
      status: 'active',
      pausedUntil: null,
      promoted: false,
    },
    {
      id: 'listing-test-7',
      title: 'Test: Studio 1-pokojowe, 30 m²',
      address: 'ul. Francuska 14, Warszawa',
      area: 30,
      rooms: 1,
      floor: 2,
      price: 2400,
      monthlyFees: 380,
      views: 143,
      saves: 13,
      addedDate: '2025-06-09',
      image: cityImage,
      imagePosition: '57% 49%',
      category: 'rent',
      status: 'paused',
      pausedUntil: '2026-07-10T09:00:00.000Z',
      promoted: false,
    },
  ];

  const [activeTab, setActiveTab] = useState<ListingTab>('all');
  const [query, setQuery] = useState('');
  const [feedback, setFeedback] = useState('');
  const [deleteModalListingId, setDeleteModalListingId] = useState<string | null>(null);
  const [deleteReason, setDeleteReason] = useState<string>('');
  const [pauseModalListingId, setPauseModalListingId] = useState<string | null>(null);
  const [pauseDuration, setPauseDuration] = useState<'7' | '14' | '30' | 'manual'>('7');
  const [listings, setListings] = useState<ListingItem[]>(() => {
    try {
      const raw = window.localStorage.getItem(listingsStorageKey);
      if (!raw) {
        return listingDefaults;
      }
      const parsed = JSON.parse(raw);
      return Array.isArray(parsed) ? parsed : listingDefaults;
    } catch {
      return listingDefaults;
    }
  });

  useEffect(() => {
    window.localStorage.setItem(listingsStorageKey, JSON.stringify(listings));
  }, [listings]);

  // Prawdziwe ogloszenia uzytkownika z backendu (/listings/mine) - scalane z makietami ponizej.
  const [apiListings, setApiListings] = useState<ApiListingSummary[]>([]);
  useEffect(() => {
    let cancelled = false;
    apiFetch<ApiListingSummary[]>('/listings/mine')
      .then((data) => { if (!cancelled) setApiListings(data); })
      .catch(() => { if (!cancelled) setApiListings([]); });
    return () => { cancelled = true; };
  }, []);

  const isApiId = (id: string) => id.startsWith('api-');
  const apiIdNum = (id: string) => Number(id.slice(4));

  // Ulubione uzytkownika (localStorage) - do statystyki "zapisanych" dla realnych ogloszen.
  const { user } = useAuth();
  const favoriteListingIds = useMemo(
    () => new Set(readUserJsonStorage<FavoriteListing[]>(user, FAVORITE_LISTINGS_STORAGE_SUFFIX, []).map((fav) => fav.listingId)),
    [user],
  );

  const apiRows: ListingItem[] = apiListings.map((summary) => ({
    id: `api-${summary.id}`,
    title: summary.title,
    address: [summary.address, summary.district, summary.city].filter(Boolean).join(', ') || summary.city,
    area: summary.area,
    rooms: summary.rooms,
    floor: summary.floor ? (parseInt(summary.floor, 10) || 0) : 0,
    price: summary.price,
    monthlyFees: 0,
    views: summary.viewsCount ?? 0,
    saves: favoriteListingIds.has(summary.id) ? 1 : 0,
    addedDate: (summary.createdAt || '').slice(0, 10) || '2025-01-01',
    image: summary.coverPhoto || heroImage,
    imagePosition: 'center',
    category: summary.offerType === 'RENT' ? 'rent' : 'sale',
    status: summary.status === 'PAUSED' ? 'paused' : 'active',
    pausedUntil: null,
    promoted: false,
    isApi: true,
    apiId: summary.id,
    apiStatus: summary.status,
  }));

  // Prawdziwe ogloszenia na gorze listy, pod nimi makiety.
  const allRows: ListingItem[] = [...apiRows, ...listings];

  const apiSetStatus = (id: string, status: ApiListingStatus) => {
    const numericId = apiIdNum(id);
    apiFetch<ApiListingSummary>(`/listings/${numericId}/status?status=${status}`, { method: 'PATCH' })
      .then((updated) => {
        setApiListings((current) => current.map((item) => (item.id === numericId ? { ...item, status: updated.status } : item)));
        setFeedback(status === 'PAUSED' ? 'Ogłoszenie zostało wstrzymane. Wznowisz je ręcznie.' : 'Ogłoszenie zostało wznowione i jest znów widoczne.');
      })
      .catch((error) => setFeedback(error instanceof Error ? error.message : 'Nie udało się zmienić statusu ogłoszenia.'));
  };

  const apiDeleteListing = (id: string) => {
    const numericId = apiIdNum(id);
    apiFetch<void>(`/listings/${numericId}`, { method: 'DELETE' })
      .then(() => {
        setApiListings((current) => current.filter((item) => item.id !== numericId));
        setFeedback('Ogłoszenie zostało trwale usunięte z serwisu.');
      })
      .catch((error) => setFeedback(error instanceof Error ? error.message : 'Nie udało się usunąć ogłoszenia.'))
      .finally(() => closeDeleteModal());
  };

  useEffect(() => {
    const resumePausedListings = () => {
      const nowMs = Date.now();
      let resumedCount = 0;

      setListings((current) => current.map((item) => {
        if (item.status !== 'paused' || !item.pausedUntil) {
          return item;
        }

        const pausedUntilMs = new Date(item.pausedUntil).getTime();
        if (Number.isNaN(pausedUntilMs) || pausedUntilMs > nowMs) {
          return item;
        }

        resumedCount += 1;
        return { ...item, status: 'active', pausedUntil: null };
      }));

      if (resumedCount > 0) {
        setFeedback(resumedCount === 1
          ? 'Wstrzymane ogłoszenie zostało automatycznie przywrócone.'
          : `${resumedCount} ogłoszenia zostały automatycznie przywrócone.`);
      }
    };

    resumePausedListings();
    const intervalId = window.setInterval(resumePausedListings, 60 * 1000);
    return () => window.clearInterval(intervalId);
  }, []);

  useEffect(() => {
    const requiredTestIds = ['listing-test-1', 'listing-test-2', 'listing-test-3', 'listing-test-4', 'listing-test-5', 'listing-test-6', 'listing-test-7'];
    setListings((current) => {
      const existingIds = new Set(current.map((listing) => listing.id));
      const missing = listingDefaults.filter((listing) => requiredTestIds.includes(listing.id) && !existingIds.has(listing.id));
      if (missing.length === 0) {
        return current;
      }
      return [...current, ...missing];
    });
  }, []);

  useEffect(() => {
    if (!feedback) {
      return;
    }
    const timeoutId = window.setTimeout(() => setFeedback(''), 3500);
    return () => window.clearTimeout(timeoutId);
  }, [feedback]);

  const parseDate = (dateValue: string) => new Date(`${dateValue}T00:00:00`).getTime();
  const formatDate = (dateValue: string) => new Date(`${dateValue}T00:00:00`).toLocaleDateString('pl-PL');
  const formatPausedUntil = (pausedUntil?: string | null) => {
    if (!pausedUntil) {
      return 'do odwołania';
    }
    const date = new Date(pausedUntil);
    if (Number.isNaN(date.getTime())) {
      return 'do odwołania';
    }
    return `do ${date.toLocaleDateString('pl-PL')}`;
  };

  const tabCounts = {
    all: allRows.filter((item) => item.status !== 'archived').length,
    sale: allRows.filter((item) => item.category === 'sale' && item.status === 'active').length,
    rent: allRows.filter((item) => item.category === 'rent' && item.status === 'active').length,
    archived: allRows.filter((item) => item.status === 'paused').length,
  };

  const visibleListings = allRows
    .filter((item) => {
      if (activeTab === 'archived') {
        return item.status === 'paused';
      }
      if (activeTab === 'all') {
        return item.status !== 'archived';
      }
      if (item.status !== 'active') {
        return false;
      }
      if (activeTab === 'sale') {
        return item.category === 'sale';
      }
      if (activeTab === 'rent') {
        return item.category === 'rent';
      }
      return true;
    })
    .filter((item) => {
      const normalized = query.trim().toLowerCase();
      if (!normalized) {
        return true;
      }
      return `${item.title} ${item.address}`.toLowerCase().includes(normalized);
    })
    .sort((left, right) => parseDate(right.addedDate) - parseDate(left.addedDate));

  const openListingInAddEditor = (listing: ListingItem) => {
    const [streetPart = '', cityPart = ''] = listing.address.split(',').map((part) => part.trim());
    const streetWithoutPrefix = streetPart.replace(/^ul\.\s*/i, '').trim();
    const streetMatch = streetWithoutPrefix.match(/^(.*?)(?:\s+(\d+[A-Za-z]?))?$/);
    const street = streetMatch?.[1]?.trim() || streetWithoutPrefix;
    const building = streetMatch?.[2]?.trim() || '';
    const roomsValue = listing.rooms >= 6 ? '6+' : String(listing.rooms);
    const floorValue = listing.floor <= 0 ? 'Parter' : listing.floor >= 8 ? '8+' : String(listing.floor);

    const draft = {
      listingDetails: {
        ...INITIAL_LISTING_DETAILS,
        transaction: listing.category === 'sale' ? 'Sprzedaż' : 'Wynajem',
        propertyType: 'Mieszkanie',
        market: 'Wtórny',
        title: listing.title,
        price: String(listing.price),
        area: String(listing.area),
        rooms: roomsValue,
        floor: floorValue,
      },
      location: {
        ...INITIAL_LISTING_LOCATION,
        city: cityPart,
        street,
        building,
      },
      extraInfo: {
        ...INITIAL_EXTRA_INFO,
        rent: listing.category === 'rent' ? String(listing.monthlyFees) : '',
      },
    };

    window.localStorage.setItem(EDIT_LISTING_DRAFT_STORAGE_KEY, JSON.stringify(draft));
    navigate(ROUTES.add);
  };

  const togglePause = (id: string) => {
    if (isApiId(id)) {
      const row = apiRows.find((item) => item.id === id);
      if (!row) {
        return;
      }
      apiSetStatus(id, row.status === 'paused' ? 'APPROVED' : 'PAUSED');
      return;
    }
    const listing = listings.find((item) => item.id === id);
    if (!listing || listing.status === 'archived') {
      return;
    }

    if (listing.status === 'paused') {
      setListings((current) => current.map((item) => (item.id === id ? { ...item, status: 'active', pausedUntil: null } : item)));
      setFeedback('Ogłoszenie zostało wznowione.');
      return;
    }

    setPauseModalListingId(id);
    setPauseDuration('7');
  };

  const closePauseModal = () => {
    setPauseModalListingId(null);
    setPauseDuration('7');
  };

  const pauseOptions = [
    { id: '7' as const, title: '7 dni', description: 'Wstrzymanie na tydzień' },
    { id: '14' as const, title: '14 dni', description: 'Wstrzymanie na dwa tygodnie' },
    { id: '30' as const, title: '30 dni', description: 'Wstrzymanie na miesiąc' },
    { id: 'manual' as const, title: 'Do odwołania', description: 'Wznowisz ręcznie, kiedy będziesz gotowy' },
  ];

  const pauseUntilLabel = (duration: '7' | '14' | '30' | 'manual') => {
    if (duration === 'manual') {
      return 'do odwołania';
    }
    const date = new Date();
    date.setDate(date.getDate() + Number(duration));
    return `do ${date.toLocaleDateString('pl-PL')}`;
  };

  const confirmPauseListing = () => {
    if (!pauseModalListingId) {
      return;
    }
    const pausedUntil = pauseDuration === 'manual'
      ? null
      : new Date(Date.now() + Number(pauseDuration) * 24 * 60 * 60 * 1000).toISOString();

    setListings((current) => current.map((item) => (item.id === pauseModalListingId
      ? { ...item, status: 'paused', pausedUntil }
      : item)));
    setFeedback(`Ogłoszenie zostało wstrzymane ${pauseUntilLabel(pauseDuration)}.`);
    closePauseModal();
  };

  const deleteReasons = [
    { id: 'sold', title: 'Ogłoszenie zostało już sprzedane / wynajęte', description: 'Transakcja została zakończona.' },
    { id: 'outdated', title: 'Nieaktualne dane', description: 'Cena, opis lub dostępność oferty się zmieniły.' },
    { id: 'new-version', title: 'Wystawię ogłoszenie ponownie', description: 'Chcę dodać nową wersję tej oferty od zera.' },
    { id: 'quality', title: 'Niewystarczająca jakość ogłoszenia', description: 'Zdjęcia lub treść wymagają większej przebudowy.' },
    { id: 'other', title: 'Inny powód', description: 'Nie pasuje żadna z powyższych opcji.' },
  ];

  const openDeleteModal = (id: string) => {
    setDeleteModalListingId(id);
    setDeleteReason('');
  };

  const closeDeleteModal = () => {
    setDeleteModalListingId(null);
    setDeleteReason('');
  };

  const removeListing = (id: string) => {
    if (isApiId(id)) {
      apiDeleteListing(id);
      return;
    }
    setListings((current) => current.filter((item) => item.id !== id));
    const selectedReason = deleteReasons.find((reason) => reason.id === deleteReason);
    setFeedback(selectedReason ? `Ogłoszenie zostało usunięte (${selectedReason.title.toLowerCase()}).` : 'Ogłoszenie zostało usunięte.');
    closeDeleteModal();
  };

  const togglePromote = (id: string) => {
    setListings((current) => current.map((item) => (item.id === id ? { ...item, promoted: !item.promoted } : item)));
  };

  const statusLabel = (status: ListingStatus) => {
    if (status === 'active') {
      return 'Aktywne';
    }
    if (status === 'paused') {
      return 'Wstrzymane';
    }
    return 'Archiwalne';
  };

  const statusClass = (status: ListingStatus) => {
    if (status === 'active') {
      return 'active';
    }
    if (status === 'paused') {
      return 'paused';
    }
    return 'archived';
  };

  return (
    <section className="account-settings-page account-listings-page" aria-label="Moje oferty">
      <div className="account-layout">
        <AccountSidebar />

        <div className="account-main account-settings-main listings-main">
          <header className="settings-head listings-head">
            <h1>Moje oferty</h1>
            <p>Zarządzaj swoimi ogłoszeniami. Edytuj, promuj lub wstrzymuj oferty.</p>
          </header>

          <section className="listings-toolbar-card">
            <div className="listings-tabs" role="tablist" aria-label="Typ ofert">
              <button type="button" role="tab" className={activeTab === 'all' ? 'active' : ''} aria-selected={activeTab === 'all'} onClick={() => setActiveTab('all')}>Wszystkie ({tabCounts.all})</button>
              <button type="button" role="tab" className={activeTab === 'sale' ? 'active' : ''} aria-selected={activeTab === 'sale'} onClick={() => setActiveTab('sale')}>Na sprzedaż ({tabCounts.sale})</button>
              <button type="button" role="tab" className={activeTab === 'rent' ? 'active' : ''} aria-selected={activeTab === 'rent'} onClick={() => setActiveTab('rent')}>Na wynajem ({tabCounts.rent})</button>
              <button type="button" role="tab" className={activeTab === 'archived' ? 'active' : ''} aria-selected={activeTab === 'archived'} onClick={() => setActiveTab('archived')}>Wstrzymane ({tabCounts.archived})</button>
            </div>

            <div className="listings-actions-row">
              <label className="listings-search"><Icon name="search" /><input type="text" value={query} onChange={(event) => setQuery(event.target.value)} placeholder="Szukaj po nazwie lub adresie" /></label>
            </div>
          </section>

          {feedback && <p className="listings-feedback">{feedback}</p>}

          <section className="listings-table" aria-label="Lista moich ofert">
            {visibleListings.length === 0 && (
              <div className="listings-empty">
                <strong>Brak ofert dla wybranego widoku.</strong>
                <small>Zmień filtry albo dodaj nowe ogłoszenie.</small>
              </div>
            )}

            {visibleListings.map((item) => (
              <article key={item.id} className="listing-row">
                <div
                  className={`listing-photo ${item.isApi ? 'clickable' : ''}`}
                  style={{ backgroundImage: `url(${item.image})`, backgroundPosition: item.imagePosition }}
                  onClick={item.isApi && item.apiId != null ? () => onOpenListing(item.apiId as number) : undefined}
                  role={item.isApi ? 'button' : undefined}
                  aria-label={item.isApi ? `Podgląd ogłoszenia ${item.title}` : undefined}
                />

                <div className="listing-main-copy">
                  <h3>{item.title}</h3>
                  <p>{item.address}</p>
                  <small>{item.area} m² • {item.rooms} pokoje • {item.floor} piętro</small>
                </div>

                <div className="listing-price-box">
                  <>
                    <strong>{item.category === 'sale' ? formatPln(item.price) : `${item.price.toLocaleString('pl-PL')} zł / miesiąc`}</strong>
                    <small>{item.monthlyFees > 0 ? `+ ${item.monthlyFees.toLocaleString('pl-PL')} zł opłaty` : `${Math.round(item.price / item.area).toLocaleString('pl-PL')} zł/m²`}</small>
                  </>
                </div>

                <div className={`listing-status ${statusClass(item.status)}`}>
                  <span>
                    {item.isApi && (item.apiStatus === 'PENDING' || item.apiStatus === 'REJECTED') ? (
                      LISTING_STATUS_LABELS[item.apiStatus]
                    ) : item.status === 'paused' ? (
                      <>
                        Wstrzymane
                        <br />
                        ({formatPausedUntil(item.pausedUntil)})
                      </>
                    ) : statusLabel(item.status)}
                  </span>
                  <small>Dodane: {formatDate(item.addedDate)}</small>
                  {item.isApi && <em className="listing-live-tag">W serwisie</em>}
                  {item.promoted && <em>Promowane</em>}
                </div>

                <div className="listing-metrics">
                  <strong><Icon name="eye" /> {item.views}</strong>
                  <small>wyświetleń</small>
                </div>

                <div className="listing-metrics">
                  <strong><Icon name="heart" /> {item.saves}</strong>
                  <small>zapisanych</small>
                </div>

                <div className="listing-actions">
                  {item.status !== 'archived' && <button type="button" onClick={() => openListingInAddEditor(item)}>Edytuj</button>}
                  {(item.isApi ? (item.apiStatus === 'APPROVED' || item.apiStatus === 'PAUSED') : item.status !== 'archived') && <button type="button" className="ghost" onClick={() => togglePause(item.id)}>{item.status === 'active' ? 'Wstrzymaj' : 'Wznów'}</button>}
                  <button type="button" className="ghost danger" onClick={() => openDeleteModal(item.id)}>Usuń</button>
                  {!item.isApi && <button type="button" className="ghost" onClick={() => togglePromote(item.id)}>{item.promoted ? 'Wyłącz promo' : 'Promuj'}</button>}
                </div>
              </article>
            ))}
          </section>

          {deleteModalListingId && (
            <div className="listing-delete-overlay" role="dialog" aria-modal="true" aria-label="Usuń ogłoszenie">
              <div className="listing-delete-modal">
                <button type="button" className="listing-delete-close" aria-label="Zamknij" onClick={closeDeleteModal}>×</button>
                <div className="listing-delete-icon"><Icon name="trash" /></div>
                <h2>Usuń ogłoszenie</h2>
                <p>Czy na pewno chcesz usunąć to ogłoszenie?<br />Ta akcja jest nieodwracalna.</p>

                <div className="listing-delete-reasons">
                  <strong>Dlaczego usuwasz to ogłoszenie? <small>(opcjonalnie)</small></strong>
                  {deleteReasons.map((reason) => (
                    <button key={reason.id} type="button" className={deleteReason === reason.id ? 'active' : ''} onClick={() => setDeleteReason(reason.id)}>
                      <span>
                        <b>{reason.title}</b>
                        <small>{reason.description}</small>
                      </span>
                      <i aria-hidden="true" />
                    </button>
                  ))}
                </div>

                <div className="listing-delete-actions">
                  <button type="button" className="secondary" onClick={closeDeleteModal}>Anuluj</button>
                  <button type="button" className="danger" onClick={() => removeListing(deleteModalListingId)}>Usuń ogłoszenie</button>
                </div>
              </div>
            </div>
          )}

          {pauseModalListingId && (
            <div className="listing-delete-overlay" role="dialog" aria-modal="true" aria-label="Wstrzymaj ogłoszenie">
              <div className="listing-pause-modal">
                <button type="button" className="listing-delete-close" aria-label="Zamknij" onClick={closePauseModal}>×</button>
                <div className="listing-pause-icon"><Icon name="clock" /></div>
                <h2>Wstrzymaj ogłoszenie</h2>
                <p className="listing-pause-description">Twoje ogłoszenie zostanie tymczasowo ukryte.<br />Możesz je wznowić w dowolnym momencie.</p>

                <div className="listing-pause-options">
                  <strong>Na jaki czas chcesz wstrzymać ogłoszenie?</strong>
                  {pauseOptions.map((option) => (
                    <button key={option.id} type="button" className={pauseDuration === option.id ? 'active' : ''} onClick={() => setPauseDuration(option.id)}>
                      <span className="listing-pause-option-icon"><Icon name={option.id === 'manual' ? 'clock' : 'calendar'} /></span>
                      <span>
                        <b>{option.title}</b>
                        <small>{option.id === 'manual' ? option.description : pauseUntilLabel(option.id)}</small>
                      </span>
                      <i aria-hidden="true" />
                    </button>
                  ))}
                </div>

                <p className="listing-pause-note"><Icon name="spark" /> Ogłoszenie będzie niewidoczne dla innych użytkowników, ale wszystkie dane i statystyki zostaną zachowane.</p>

                <div className="listing-delete-actions">
                  <button type="button" className="secondary" onClick={closePauseModal}>Anuluj</button>
                  <button type="button" className="confirm" onClick={confirmPauseListing}>Wstrzymaj ogłoszenie</button>
                </div>
              </div>
            </div>
          )}
        </div>
      </div>
    </section>
  );
}

function AccountSettingsPage() {
  const navigate = useNavigate();
  const { user } = useAuth();
  const [accountSwitches, setAccountSwitches] = useState([true, true, false, false]);
  // Indeks 4 ("Oferty promowane i newsletter") to zgoda marketingowa e-mail zapisywana na serwerze.
  const [mailSwitches, setMailSwitches] = useState([true, true, true, false, false]);
  const [smsConsent, setSmsConsent] = useState(true);
  const address = user?.address ?? '';
  const birthDate = user?.birthDate ?? '';

  // Wczytaj aktualną zgodę marketingową użytkownika (steruje kwalifikacją do kampanii).
  useEffect(() => {
    if (!user) {
      return;
    }
    let active = true;
    apiFetch<{ emailConsent: boolean; smsConsent: boolean }>('/me/marketing')
      .then((consent) => {
        if (!active) {
          return;
        }
        setSmsConsent(consent.smsConsent);
        setMailSwitches((current) => current.map((value, idx) => (idx === 4 ? consent.emailConsent : value)));
      })
      .catch(() => {
        // Brak zgody/sesji - zostawiamy wartości domyślne.
      });
    return () => {
      active = false;
    };
  }, [user]);

  const toggleAccountSwitch = (index: number) => {
    setAccountSwitches((current) => current.map((value, idx) => (idx === index ? !value : value)));
  };

  const toggleMailSwitch = (index: number) => {
    setMailSwitches((current) => {
      const next = current.map((value, idx) => (idx === index ? !value : value));
      // Zgodę marketingową e-mail (indeks 4) utrwalamy na serwerze.
      if (index === 4) {
        apiFetch('/me/marketing', {
          method: 'PUT',
          body: JSON.stringify({ emailConsent: next[4], smsConsent }),
        }).catch(() => {
          // Błąd zapisu nie blokuje UI; wartość zostanie zsynchronizowana przy kolejnym wejściu.
        });
      }
      return next;
    });
  };

  return (
    <section className="account-settings-page" aria-label="Dane i ustawienia">
      <div className="account-layout">
        <AccountSidebar />

        <div className="account-main account-settings-main">
          <header className="settings-head">
            <h1>Dane i ustawienia</h1>
            <p>Zarządzaj swoimi danymi osobowymi, preferencjami i ustawieniami konta.</p>
          </header>

          <div className="settings-grid">
        <section className="settings-card">
          <div className="settings-card-head">
            <h2>Moje dane</h2>
          </div>
          <div className="settings-profile-row">
            <span><Icon name="user" /></span>
            <div>
              <strong>{getDisplayName(user)}</strong>
              <small>Konto Premium</small>
            </div>
            <Link to={ROUTES.accountProfileEdit}>Edytuj</Link>
          </div>
          <div className="settings-list">
            <article><span><Icon name="mail" /></span><em>Adres e-mail</em><strong>{user?.email}</strong><Icon name="chevron" /></article>
            <article><span><Icon name="phone" /></span><em>Numer telefonu</em><strong>{window.localStorage.getItem(accountStorageKey('Phone', user)) || user?.phone || 'Nie podano'}</strong><Icon name="chevron" /></article>
            <article><span><Icon name="calendar" /></span><em>Data urodzenia</em><strong>{birthDate ? birthDate.split('-').reverse().join('.') : 'Nie podano'}</strong><Icon name="chevron" /></article>
            <article><span><Icon name="pin" /></span><em>Adres zamieszkania</em><strong>{address || 'Nie podano'}</strong><Icon name="chevron" /></article>
            <article><span><Icon name="message" /></span><em>Preferencje kontaktu</em><strong>{contactPreferenceLabel(user?.contactPreference)}</strong><Icon name="chevron" /></article>
            <article><span><Icon name="search" /></span><em>Język komunikacji</em><strong>{preferredLanguageLabel(user?.preferredLanguage)}</strong><Icon name="chevron" /></article>
          </div>
        </section>

        <section className="settings-card">
          <div className="settings-card-head split">
            <h2>Preferencje wyszukiwania</h2>
            <button type="button">Edytuj</button>
          </div>
          <div className="settings-list compact">
            <article><span><Icon name="pin" /></span><em>Lokalizacje</em><strong>Warszawa (Mokotów, Śródmieście, Wola)</strong><Icon name="chevron" /></article>
            <article><span><Icon name="building" /></span><em>Typ nieruchomości</em><strong>Mieszkania</strong><Icon name="chevron" /></article>
            <article><span><Icon name="coin" /></span><em>Budżet</em><strong>do 800 000 zł</strong><Icon name="chevron" /></article>
            <article><span><Icon name="calculator" /></span><em>Metraż</em><strong>od 30 m² do 80 m²</strong><Icon name="chevron" /></article>
            <article><span><Icon name="house" /></span><em>Liczba pokoi</em><strong>2 - 4 pokoje</strong><Icon name="chevron" /></article>
          </div>
        </section>

        <section className="settings-card">
          <div className="settings-card-head">
            <h2>Ustawienia konta</h2>
          </div>
          <div className="settings-list compact">
            <article><span><Icon name="search" /></span><em>Język</em><strong>{preferredLanguageLabel(user?.preferredLanguage)}</strong><Icon name="chevron" /></article>
            <article><span><Icon name="coin" /></span><em>Waluta</em><strong>PLN (zł)</strong><Icon name="chevron" /></article>
            <article><span><Icon name="calculator" /></span><em>Jednostki powierzchni</em><strong>Metry kwadratowe (m²)</strong><Icon name="chevron" /></article>
            <article className="toggle-row"><span><Icon name="shield" /></span><em>Pokaż tylko oferty bezpośrednie</em><button type="button" className={`account-toggle ${accountSwitches[0] ? 'on' : ''}`} onClick={() => toggleAccountSwitch(0)} /></article>
            <article className="toggle-row"><span><Icon name="lock" /></span><em>Ukryj oferty nieaktualne</em><button type="button" className={`account-toggle ${accountSwitches[1] ? 'on' : ''}`} onClick={() => toggleAccountSwitch(1)} /></article>
            <article className="toggle-row"><span><Icon name="house" /></span><em>Zapisywanie wyszukiwań na stronie głównej</em><button type="button" className={`account-toggle ${accountSwitches[2] ? 'on' : ''}`} onClick={() => toggleAccountSwitch(2)} /></article>
            <article className="toggle-row"><span><Icon name="moon" /></span><em>Tryb ciemny</em><button type="button" className={`account-toggle ${accountSwitches[3] ? 'on' : ''}`} onClick={() => toggleAccountSwitch(3)} /></article>
          </div>
        </section>

        <section className="settings-card">
          <div className="settings-card-head">
            <h2>Zarządzanie kontem</h2>
          </div>
          <div className="settings-list compact">
            <article><span><Icon name="lock" /></span><em>Zmień hasło</em><strong>Zaktualizuj swoje hasło do konta.</strong><Icon name="chevron" /></article>
            <article><span><Icon name="shield" /></span><em>Dwuetapowa weryfikacja</em><strong>Zabezpiecz konto dodatkową warstwą ochrony.</strong><button type="button" className="mini-tag">Wyłączona</button></article>
            <article><span><Icon name="screen" /></span><em>Urządzenia i sesje</em><strong>Zobacz, gdzie i na jakich urządzeniach jesteś zalogowany.</strong><Icon name="chevron" /></article>
            <article className="danger"><span><Icon name="warning" /></span><em>Usuń konto</em><strong>Trwale usuń swoje konto i wszystkie dane.</strong><Icon name="chevron" /></article>
          </div>
        </section>

        <section className="settings-card">
          <div className="settings-card-head">
            <h2>Powiadomienia e-mail</h2>
            <p>Wybierz, o czym chcesz otrzymywać powiadomienia.</p>
          </div>
          <div className="settings-list compact">
            <article className="toggle-row"><span><Icon name="bell" /></span><em>Nowe oferty z zapisanych wyszukiwań</em><button type="button" className={`account-toggle ${mailSwitches[0] ? 'on' : ''}`} onClick={() => toggleMailSwitch(0)} /></article>
            <article className="toggle-row"><span><Icon name="trend" /></span><em>Alerty cenowe</em><button type="button" className={`account-toggle ${mailSwitches[1] ? 'on' : ''}`} onClick={() => toggleMailSwitch(1)} /></article>
            <article className="toggle-row"><span><Icon name="message" /></span><em>Wiadomości od użytkowników</em><button type="button" className={`account-toggle ${mailSwitches[2] ? 'on' : ''}`} onClick={() => toggleMailSwitch(2)} /></article>
            <article className="toggle-row"><span><Icon name="gear" /></span><em>Nowości i aktualizacje</em><button type="button" className={`account-toggle ${mailSwitches[3] ? 'on' : ''}`} onClick={() => toggleMailSwitch(3)} /></article>
            <article className="toggle-row"><span><Icon name="mail" /></span><em>Oferty promowane i newsletter</em><button type="button" className={`account-toggle ${mailSwitches[4] ? 'on' : ''}`} onClick={() => toggleMailSwitch(4)} /></article>
          </div>
        </section>

        <section className="settings-card settings-export">
          <div className="settings-card-head">
            <h2>Export danych</h2>
            <p>Pobierz kopię swoich danych z konta Mieszko.</p>
          </div>
          <div className="settings-export-box">
            <span><Icon name="upload" /></span>
            <div>
              <strong>Pobierz swoje dane</strong>
              <small>Otrzymasz plik z Twoimi danymi, zapisanymi wyszukiwaniami, ulubionymi ogłoszeniami i innymi informacjami.</small>
              <button type="button">Poproś o export danych</button>
            </div>
          </div>
        </section>
          </div>

          <footer className="settings-footer-note">
            <Icon name="lock" /> Twoje dane są u nas bezpieczne. Stosujemy szyfrowanie SSL i zaawansowane środki ochrony danych.
          </footer>
        </div>
      </div>
    </section>
  );
}

function AccountSecurityPage() {
  const { user } = useAuth();
  const recentSecurityActivity = [
    {
      id: 'activity-1',
      icon: 'screen',
      tone: 'green',
      title: 'Nowe logowanie',
      details: 'Warszawa, Polska • Chrome • Windows',
      when: 'Dzisiaj, 09:15',
      status: 'To Ty',
    },
    {
      id: 'activity-2',
      icon: 'lock',
      tone: 'blue',
      title: 'Zmiana hasła',
      details: 'Warszawa, Polska • Chrome • Windows',
      when: 'Wczoraj, 16:42',
      status: 'To Ty',
    },
    {
      id: 'activity-3',
      icon: 'screen',
      tone: 'green',
      title: 'Nowe logowanie',
      details: 'Kraków, Polska • Safari • iOS',
      when: '12 maja 2025, 11:03',
      status: 'To Ty',
    },
    {
      id: 'activity-4',
      icon: 'warning',
      tone: 'orange',
      title: 'Wylogowanie ze wszystkich urządzeń',
      details: 'Warszawa, Polska • Chrome • Windows',
      when: '10 maja 2025, 15:20',
      status: 'To Ty',
    },
  ] as const;

  const securityCards = [
    {
      id: 'password',
      icon: 'lock',
      title: 'Hasło',
      description: 'Zmień swoje hasło regularnie, aby chronić konto.',
      details: 'Ostatnia zmiana: 12 maja 2025',
      action: 'Zmień hasło',
      iconTone: 'green',
      tone: 'ok',
    },
    {
      id: '2fa',
      icon: 'shield',
      title: 'Weryfikacja dwuetapowa',
      description: 'Dodaj dodatkową warstwę ochrony do swojego konta.',
      details: 'Status: Włączona',
      action: 'Zarządzaj',
      iconTone: 'blue',
      tone: 'ok',
    },
    {
      id: 'email',
      icon: 'mail',
      title: 'E-mail',
      description: 'Zarządzaj swoim adresem e-mail i ustawieniami.',
      details: user?.email || '',
      action: 'Zarządzaj',
      iconTone: 'yellow',
      tone: 'neutral',
    },
    {
      id: 'phone',
      icon: 'phone',
      title: 'Numer telefonu',
      description: 'Dodaj numer telefonu do odzyskiwania konta.',
      details: '+48 123 456 789',
      action: 'Zarządzaj',
      iconTone: 'green',
      tone: 'neutral',
    },
    {
      id: 'trusted-devices',
      icon: 'screen',
      title: 'Zaufane urządzenia',
      description: 'Zarządzaj urządzeniami, na których jesteś zalogowany.',
      details: '2 aktywne urządzenia',
      action: 'Zobacz',
      iconTone: 'purple',
      tone: 'neutral',
    },
    {
      id: 'sign-out-all',
      icon: 'trash',
      title: 'Wyloguj się ze wszystkich urządzeń',
      description: 'Zakończ wszystkie aktywne sesje na innych urządzeniach.',
      details: 'Dla zwiększenia bezpieczeństwa konta',
      action: 'Wyloguj',
      iconTone: 'pink',
      tone: 'danger',
    },
  ] as const;

  return (
    <section className="account-settings-page account-security-page" aria-label="Bezpieczeństwo">
      <div className="account-layout">
        <AccountSidebar />

        <div className="account-main account-settings-main security-main">
          <header className="settings-head security-head">
            <h1><Icon name="shield" /> Bezpieczeństwo</h1>
            <p>Zarządzaj bezpieczeństwem swojego konta i chroń swoje dane.</p>
          </header>

          <div className="security-overview-grid">
            <section className="security-card security-score-card" aria-label="Poziom bezpieczeństwa konta">
              <h2>Poziom bezpieczeństwa</h2>

              <div className="security-score-body">
                <div className="security-score-ring" style={{ ['--score' as string]: 90 }} aria-hidden="true">
                  <div>
                    <strong>90%</strong>
                    <small>Bardzo wysoki</small>
                  </div>
                </div>

                <div className="security-score-copy">
                  <strong>Świetnie! Twoje konto jest bardzo dobrze zabezpieczone.</strong>
                  <ul>
                    <li><Icon name="check" /> Silne hasło</li>
                    <li><Icon name="check" /> Weryfikacja dwuetapowa</li>
                    <li><Icon name="check" /> Zweryfikowany e-mail</li>
                    <li><Icon name="check" /> Brak podejrzanych aktywności</li>
                  </ul>
                  <button type="button">Zobacz rekomendacje</button>
                </div>
              </div>
            </section>

            <section className="security-card security-activity-card" aria-label="Ostatnia aktywność konta">
              <div className="security-card-head split">
                <h2>Ostatnia aktywność</h2>
                <button type="button">Zobacz wszystkie <Icon name="arrow" /></button>
              </div>

              <div className="security-activity-list">
                {recentSecurityActivity.map((item) => (
                  <article key={item.id}>
                    <span className={`activity-icon ${item.tone}`}><Icon name={item.icon} /></span>
                    <div>
                      <strong>{item.title}</strong>
                      <small>{item.details}</small>
                    </div>
                    <div className="security-activity-meta">
                      <em>{item.when}</em>
                      <i>{item.status}</i>
                    </div>
                  </article>
                ))}
              </div>
            </section>
          </div>

          <section className="security-settings-block" aria-label="Ustawienia bezpieczeństwa">
            <h2>Ustawienia bezpieczeństwa</h2>

            <div className="security-settings-grid">
              {securityCards.map((item) => (
                <article key={item.id} className={`security-setting-card ${item.tone} tone-${item.iconTone}`}>
                  <span><Icon name={item.icon} /></span>
                  <div>
                    <strong>{item.title}</strong>
                    <p>{item.description}</p>
                    <small>{item.details}</small>
                  </div>
                  <button type="button">{item.action}</button>
                </article>
              ))}
            </div>
          </section>
        </div>
      </div>
    </section>
  );
}

function AccountProfileEditPage() {
  const navigate = useNavigate();
  const { user, updateProfile } = useAuth();
  const { notify } = useNotifications();
  type UploadTarget = 'avatar' | 'cover';
  type MessageTone = 'success' | 'error' | 'info';

  const allowedMimeTypes = new Set(['image/jpeg', 'image/png', 'image/webp']);
  const maxImageSizeBytes = 5 * 1024 * 1024;
  const minDimensions: Record<UploadTarget, { width: number; height: number }> = {
    avatar: { width: 160, height: 160 },
    cover: { width: 960, height: 260 },
  };

  const readFileAsDataUrl = (file: File) => new Promise<string>((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => {
      if (typeof reader.result !== 'string') {
        reject(new Error('Nie udało się odczytać pliku.'));
        return;
      }
      resolve(reader.result);
    };
    reader.onerror = () => reject(new Error('Nie udało się odczytać pliku.'));
    reader.readAsDataURL(file);
  });

  const loadImage = (source: string) => new Promise<HTMLImageElement>((resolve, reject) => {
    const image = new Image();
    image.onload = () => resolve(image);
    image.onerror = () => reject(new Error('Nie udało się odczytać obrazu.'));
    image.src = source;
  });

  const calculateSkinToneRatio = (image: HTMLImageElement) => {
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d');
    if (!context) {
      return 0;
    }

    const sampleWidth = 64;
    const sampleHeight = Math.max(64, Math.round((image.height / image.width) * 64));
    canvas.width = sampleWidth;
    canvas.height = sampleHeight;
    context.drawImage(image, 0, 0, sampleWidth, sampleHeight);

    const { data } = context.getImageData(0, 0, sampleWidth, sampleHeight);
    let skinPixels = 0;
    const totalPixels = data.length / 4;

    for (let index = 0; index < data.length; index += 4) {
      const red = data[index];
      const green = data[index + 1];
      const blue = data[index + 2];
      const max = Math.max(red, green, blue);
      const min = Math.min(red, green, blue);
      const isSkinTone = red > 95 && green > 40 && blue > 20 && max - min > 15 && Math.abs(red - green) > 15 && red > green && red > blue;
      if (isSkinTone) {
        skinPixels += 1;
      }
    }

    return totalPixels > 0 ? skinPixels / totalPixels : 0;
  };

  const verifyUploadedImage = async (file: File, target: UploadTarget) => {
    if (!allowedMimeTypes.has(file.type)) {
      return { ok: false as const, reason: 'Dozwolone są tylko pliki JPG, PNG lub WEBP.' };
    }

    if (file.size > maxImageSizeBytes) {
      return { ok: false as const, reason: 'Plik jest za duży. Maksymalny rozmiar to 5 MB.' };
    }

    if (/(nude|naked|porn|xxx|sex|erotic|nago|gola|goly|nagie)/i.test(file.name)) {
      return { ok: false as const, reason: 'Nazwa pliku wskazuje na niedozwoloną treść.' };
    }

    try {
      const dataUrl = await readFileAsDataUrl(file);
      const image = await loadImage(dataUrl);
      if (image.width < minDimensions[target].width || image.height < minDimensions[target].height) {
        return {
          ok: false as const,
          reason: `Zdjęcie ma za małą rozdzielczość. Minimum: ${minDimensions[target].width}x${minDimensions[target].height}px.`,
        };
      }

      const skinToneRatio = calculateSkinToneRatio(image);
      const skinToneThreshold = target === 'avatar' ? 0.82 : 0.76;
      if (skinToneRatio > skinToneThreshold) {
        return { ok: false as const, reason: 'Zdjęcie nie przeszło automatycznej weryfikacji bezpieczeństwa.' };
      }

      return { ok: true as const, dataUrl };
    } catch {
      return { ok: false as const, reason: 'Nie udało się zweryfikować zdjęcia. Spróbuj ponownie.' };
    }
  };

  const phoneCountryOptions = [
    { iso2: 'pl', code: '+48', country: 'Polska', digits: 9 },
    { iso2: 'al', code: '+355', country: 'Albania', digits: 9 },
    { iso2: 'at', code: '+43', country: 'Austria', digits: 10 },
    { iso2: 'be', code: '+32', country: 'Belgia', digits: 9 },
    { iso2: 'bg', code: '+359', country: 'Bułgaria', digits: 9 },
    { iso2: 'hr', code: '+385', country: 'Chorwacja', digits: 9 },
    { iso2: 'cy', code: '+357', country: 'Cypr', digits: 8 },
    { iso2: 'cz', code: '+420', country: 'Czechy', digits: 9 },
    { iso2: 'dk', code: '+45', country: 'Dania', digits: 8 },
    { iso2: 'ee', code: '+372', country: 'Estonia', digits: 8 },
    { iso2: 'fi', code: '+358', country: 'Finlandia', digits: 10 },
    { iso2: 'fr', code: '+33', country: 'Francja', digits: 9 },
    { iso2: 'de', code: '+49', country: 'Niemcy', digits: 11 },
    { iso2: 'gr', code: '+30', country: 'Grecja', digits: 10 },
    { iso2: 'hu', code: '+36', country: 'Węgry', digits: 9 },
    { iso2: 'is', code: '+354', country: 'Islandia', digits: 7 },
    { iso2: 'ie', code: '+353', country: 'Irlandia', digits: 9 },
    { iso2: 'it', code: '+39', country: 'Włochy', digits: 10 },
    { iso2: 'lv', code: '+371', country: 'Łotwa', digits: 8 },
    { iso2: 'lt', code: '+370', country: 'Litwa', digits: 8 },
    { iso2: 'lu', code: '+352', country: 'Luksemburg', digits: 9 },
    { iso2: 'mt', code: '+356', country: 'Malta', digits: 8 },
    { iso2: 'nl', code: '+31', country: 'Holandia', digits: 9 },
    { iso2: 'no', code: '+47', country: 'Norwegia', digits: 8 },
    { iso2: 'pt', code: '+351', country: 'Portugalia', digits: 9 },
    { iso2: 'ro', code: '+40', country: 'Rumunia', digits: 9 },
    { iso2: 'rs', code: '+381', country: 'Serbia', digits: 9 },
    { iso2: 'sk', code: '+421', country: 'Słowacja', digits: 9 },
    { iso2: 'si', code: '+386', country: 'Słowenia', digits: 8 },
    { iso2: 'es', code: '+34', country: 'Hiszpania', digits: 9 },
    { iso2: 'se', code: '+46', country: 'Szwecja', digits: 9 },
    { iso2: 'ch', code: '+41', country: 'Szwajcaria', digits: 9 },
    { iso2: 'ua', code: '+380', country: 'Ukraina', digits: 9 },
    { iso2: 'gb', code: '+44', country: 'Wielka Brytania', digits: 10 },
    { iso2: 'tr', code: '+90', country: 'Turcja', digits: 10 },
    { iso2: 'us', code: '+1', country: 'USA/Kanada', digits: 10 },
    { iso2: 'mx', code: '+52', country: 'Meksyk', digits: 10 },
    { iso2: 'br', code: '+55', country: 'Brazylia', digits: 11 },
    { iso2: 'ar', code: '+54', country: 'Argentyna', digits: 10 },
    { iso2: 'cl', code: '+56', country: 'Chile', digits: 9 },
    { iso2: 'co', code: '+57', country: 'Kolumbia', digits: 10 },
    { iso2: 'au', code: '+61', country: 'Australia', digits: 9 },
    { iso2: 'nz', code: '+64', country: 'Nowa Zelandia', digits: 9 },
    { iso2: 'jp', code: '+81', country: 'Japonia', digits: 10 },
    { iso2: 'kr', code: '+82', country: 'Korea Południowa', digits: 10 },
    { iso2: 'cn', code: '+86', country: 'Chiny', digits: 11 },
    { iso2: 'in', code: '+91', country: 'Indie', digits: 10 },
    { iso2: 'pk', code: '+92', country: 'Pakistan', digits: 10 },
    { iso2: 'id', code: '+62', country: 'Indonezja', digits: 10 },
    { iso2: 'th', code: '+66', country: 'Tajlandia', digits: 9 },
    { iso2: 'vn', code: '+84', country: 'Wietnam', digits: 9 },
    { iso2: 'sg', code: '+65', country: 'Singapur', digits: 8 },
    { iso2: 'my', code: '+60', country: 'Malezja', digits: 10 },
    { iso2: 'za', code: '+27', country: 'RPA', digits: 9 },
    { iso2: 'eg', code: '+20', country: 'Egipt', digits: 10 },
    { iso2: 'ma', code: '+212', country: 'Maroko', digits: 9 },
    { iso2: 'ng', code: '+234', country: 'Nigeria', digits: 10 },
    { iso2: 'ke', code: '+254', country: 'Kenia', digits: 9 },
    { iso2: 'ae', code: '+971', country: 'ZEA', digits: 9 },
    { iso2: 'sa', code: '+966', country: 'Arabia Saudyjska', digits: 9 },
    { iso2: 'il', code: '+972', country: 'Izrael', digits: 9 },
    { iso2: 'qa', code: '+974', country: 'Katar', digits: 8 },
  ];

  const [profile, setProfile] = useState({
    firstName: getFirstName(getDisplayName(user)),
    lastName: getLastName(getDisplayName(user)),
    nick: (user?.email || 'uzytkownik').split('@')[0],
    email: user?.email || '',
    phonePrefix: window.localStorage.getItem(accountStorageKey('PhonePrefix', user)) ?? '+48',
    phone: (window.localStorage.getItem(accountStorageKey('PhoneDigits', user)) ?? (user?.phone || '').replace(/\D/g, '')),
    birthDate: user?.birthDate ?? window.localStorage.getItem(accountStorageKey('BirthDate', user)) ?? '',
    address: user?.address ?? window.localStorage.getItem(accountStorageKey('Address', user)) ?? '',
    contactPreference: user?.contactPreference ?? 'EMAIL_AND_PHONE',
    preferredLanguage: user?.preferredLanguage ?? 'PL',
    bio: '',
  });
  const [profilePhotoName, setProfilePhotoName] = useState(() => window.localStorage.getItem(accountStorageKey('AvatarName', user)) ?? '');
  const [profilePhotoImage, setProfilePhotoImage] = useState(() => window.localStorage.getItem(accountStorageKey('AvatarImage', user)) ?? '');
  const profilePhotoInputRef = useRef<HTMLInputElement | null>(null);
  const phoneCountryRef = useRef<HTMLDivElement | null>(null);
  const [coverPhotoName, setCoverPhotoName] = useState(() => window.localStorage.getItem(accountStorageKey('CoverName', user)) ?? '');
  const coverPhotoInputRef = useRef<HTMLInputElement | null>(null);
  const [message, setMessage] = useState('');
  const [messageTone, setMessageTone] = useState<MessageTone>('success');
  const [isPhoneCountryOpen, setIsPhoneCountryOpen] = useState(false);
  const [isPhoneTouched, setIsPhoneTouched] = useState(false);
  const [isPhoneVerified, setIsPhoneVerified] = useState(false);
  const [profileVisibility, setProfileVisibility] = useState<'publiczna' | 'prywatna' | 'kontakty'>('publiczna');
  const [isPhoneVerificationModalOpen, setIsPhoneVerificationModalOpen] = useState(false);
  const [phoneVerificationStep, setPhoneVerificationStep] = useState<1 | 2 | 3>(1);
  const [verificationCodeDigits, setVerificationCodeDigits] = useState(() => Array.from({ length: 6 }, () => ''));
  const [verificationResendCooldown, setVerificationResendCooldown] = useState(28);
  const [verificationCodeError, setVerificationCodeError] = useState('');
  const verificationInputRefs = useRef<Array<HTMLInputElement | null>>([]);

  const selectedPhoneCountry = phoneCountryOptions.find((option) => option.code === profile.phonePrefix) ?? phoneCountryOptions[0];
  const selectedPhoneDigitsRequired = selectedPhoneCountry.digits;
  const phoneDigitsOnly = profile.phone.replace(/\D/g, '');
  const isPhoneNumberComplete = phoneDigitsOnly.length === selectedPhoneDigitsRequired;
  const shouldShowPhoneError = isPhoneTouched && !isPhoneNumberComplete;

  const phoneMaskGroups = (() => {
    if (selectedPhoneDigitsRequired <= 7) {
      return [3, selectedPhoneDigitsRequired - 3];
    }
    if (selectedPhoneDigitsRequired === 8) {
      return [4, 4];
    }
    if (selectedPhoneDigitsRequired === 9) {
      return [3, 3, 3];
    }
    if (selectedPhoneDigitsRequired === 10) {
      return [3, 3, 4];
    }
    return [3, 4, selectedPhoneDigitsRequired - 7];
  })();

  const formatPhoneWithMask = (digits: string) => {
    let cursor = 0;
    return phoneMaskGroups
      .map((groupLength) => {
        const chunk = digits.slice(cursor, cursor + groupLength);
        cursor += groupLength;
        return chunk;
      })
      .filter(Boolean)
      .join('-');
  };

  const phoneMaskPlaceholder = phoneMaskGroups.map((groupLength) => '-'.repeat(groupLength)).join('-');
  const phoneDisplayValue = formatPhoneWithMask(phoneDigitsOnly);
  const phoneSmsDisplayValue = formatPhoneWithMask(phoneDigitsOnly).replace(/-/g, ' ');
  const todayIsoDate = new Date().toISOString().slice(0, 10);

  const setField = (field: keyof typeof profile, value: string) => {
    setProfile((current) => ({ ...current, [field]: value }));
  };

  const showTemporaryMessage = (text: string, tone: MessageTone = 'success') => {
    setMessageTone(tone);
    setMessage(text);
    setTimeout(() => setMessage(''), 2400);
  };

  const saveProfile = async () => {
    if (phoneDigitsOnly.length > 0 && !isPhoneNumberComplete) {
      setIsPhoneTouched(true);
      showTemporaryMessage(`Numer telefonu dla kraju ${selectedPhoneCountry.country} musi mieć ${selectedPhoneDigitsRequired} cyfr.`, 'error');
      return;
    }
    const fullPhone = profile.phone ? `${profile.phonePrefix} ${profile.phone}` : '';
    try {
      await updateProfile(
        `${profile.firstName} ${profile.lastName}`.trim(),
        fullPhone || undefined,
        profile.birthDate || undefined,
        profile.address.trim() || undefined,
        profile.contactPreference as ContactPreference,
        profile.preferredLanguage as PreferredLanguage,
      );
    } catch (error) {
      showTemporaryMessage(error instanceof Error ? error.message : 'Nie udało się zapisać zmian.', 'error');
      return;
    }
    window.localStorage.setItem(accountStorageKey('PhonePrefix', user), profile.phonePrefix);
    window.localStorage.setItem(accountStorageKey('PhoneDigits', user), profile.phone);
    window.localStorage.setItem(accountStorageKey('Phone', user), fullPhone);
    showTemporaryMessage('Zmiany zapisane pomyślnie.', 'success');
  };

  const handlePhoneInputChange = (rawValue: string) => {
    const digits = rawValue.replace(/\D/g, '').slice(0, selectedPhoneDigitsRequired);
    setField('phone', digits);
    setIsPhoneTouched(true);
    setIsPhoneVerified(false);
  };

  const handlePhoneCountryChange = (countryCode: string) => {
    const nextCountry = phoneCountryOptions.find((option) => option.code === countryCode);
    if (!nextCountry) {
      return;
    }
    const normalizedPhone = phoneDigitsOnly.slice(0, nextCountry.digits);
    setProfile((current) => ({ ...current, phonePrefix: nextCountry.code, phone: normalizedPhone }));
    setIsPhoneTouched(true);
    setIsPhoneVerified(false);
  };

  const openPhoneVerificationModal = () => {
    if (!isPhoneNumberComplete) {
      setIsPhoneTouched(true);
      return;
    }
    setPhoneVerificationStep(1);
    setVerificationCodeDigits(Array.from({ length: 6 }, () => ''));
    setVerificationResendCooldown(28);
    setVerificationCodeError('');
    setIsPhoneVerificationModalOpen(true);
  };

  const closePhoneVerificationModal = () => {
    setPhoneVerificationStep(1);
    setVerificationCodeDigits(Array.from({ length: 6 }, () => ''));
    setVerificationResendCooldown(28);
    setVerificationCodeError('');
    setIsPhoneVerificationModalOpen(false);
  };

  const sendVerificationSms = () => {
    showTemporaryMessage('Wysłano SMS z kodem potwierdzającym.', 'success');
    setVerificationCodeDigits(Array.from({ length: 6 }, () => ''));
    setVerificationResendCooldown(28);
    setVerificationCodeError('');
    setPhoneVerificationStep(2);
  };

  const handleVerificationDigitChange = (index: number, rawValue: string) => {
    const nextDigit = rawValue.replace(/\D/g, '').slice(-1);
    const nextDigits = [...verificationCodeDigits];
    nextDigits[index] = nextDigit;
    setVerificationCodeDigits(nextDigits);
    setVerificationCodeError('');
    if (nextDigit && index < verificationInputRefs.current.length - 1) {
      verificationInputRefs.current[index + 1]?.focus();
    }
  };

  const handleVerificationDigitKeyDown = (index: number, event: ReactKeyboardEvent<HTMLInputElement>) => {
    if (event.key === 'Backspace' && !verificationCodeDigits[index] && index > 0) {
      verificationInputRefs.current[index - 1]?.focus();
    }
  };

  const handleVerificationPaste = (event: ReactClipboardEvent<HTMLDivElement>) => {
    event.preventDefault();
    const pastedDigits = event.clipboardData.getData('text').replace(/\D/g, '').slice(0, 6);
    if (!pastedDigits) {
      return;
    }
    const nextDigits = Array.from({ length: 6 }, (_, index) => pastedDigits[index] ?? '');
    setVerificationCodeDigits(nextDigits);
    setVerificationCodeError('');
    const focusIndex = Math.min(pastedDigits.length, 5);
    verificationInputRefs.current[focusIndex]?.focus();
  };

  const resendVerificationSms = () => {
    if (verificationResendCooldown > 0) {
      return;
    }
    setVerificationResendCooldown(28);
    setVerificationCodeError('');
    showTemporaryMessage('Wysłano nowy kod SMS.', 'success');
  };

  const handleModeratedUpload = async (event: ReactChangeEvent<HTMLInputElement>, target: UploadTarget) => {
    const file = event.target.files?.[0];
    if (!file) {
      return;
    }

    showTemporaryMessage('Trwa weryfikacja zdjęcia...', 'info');
    const result = await verifyUploadedImage(file, target);
    if (!result.ok) {
      showTemporaryMessage(result.reason, 'error');
      event.target.value = '';
      return;
    }

    if (target === 'avatar') {
      window.localStorage.setItem(accountStorageKey('AvatarImage', user), result.dataUrl);
      window.localStorage.setItem(accountStorageKey('AvatarName', user), file.name);
      setProfilePhotoImage(result.dataUrl);
      setProfilePhotoName(file.name);
      showTemporaryMessage('Zdjęcie profilowe przeszło weryfikację i zostało zapisane.', 'success');
      return;
    }

    window.localStorage.setItem(accountStorageKey('CoverImage', user), result.dataUrl);
    window.localStorage.setItem(accountStorageKey('CoverName', user), file.name);
    setCoverPhotoName(file.name);
    showTemporaryMessage('Zdjęcie w tle przeszło weryfikację i zostało zapisane.', 'success');
  };

  const clearProfilePhoto = () => {
    window.localStorage.removeItem(accountStorageKey('AvatarImage', user));
    window.localStorage.removeItem(accountStorageKey('AvatarName', user));
    if (profilePhotoInputRef.current) {
      profilePhotoInputRef.current.value = '';
    }
    setProfilePhotoImage('');
    setProfilePhotoName('');
    showTemporaryMessage('Usunięto zdjęcie profilowe.', 'success');
  };

  const clearCoverPhoto = () => {
    window.localStorage.removeItem(accountStorageKey('CoverImage', user));
    window.localStorage.removeItem(accountStorageKey('CoverName', user));
    if (coverPhotoInputRef.current) {
      coverPhotoInputRef.current.value = '';
    }
    setCoverPhotoName('');
    showTemporaryMessage('Usunięto zdjęcie w tle.', 'success');
  };

  useEffect(() => {
    const closeOnOutsideClick = (event: MouseEvent) => {
      if (!phoneCountryRef.current) {
        return;
      }
      if (!phoneCountryRef.current.contains(event.target as Node)) {
        setIsPhoneCountryOpen(false);
      }
    };

    window.addEventListener('mousedown', closeOnOutsideClick);
    return () => window.removeEventListener('mousedown', closeOnOutsideClick);
  }, []);

  useEffect(() => {
    if (!isPhoneVerificationModalOpen || phoneVerificationStep !== 2 || verificationResendCooldown <= 0) {
      return;
    }
    const intervalId = window.setInterval(() => {
      setVerificationResendCooldown((current) => (current > 0 ? current - 1 : 0));
    }, 1000);
    return () => window.clearInterval(intervalId);
  }, [isPhoneVerificationModalOpen, phoneVerificationStep, verificationResendCooldown]);

  useEffect(() => {
    if (!isPhoneVerificationModalOpen || phoneVerificationStep !== 2) {
      return;
    }
    verificationInputRefs.current[0]?.focus();
  }, [isPhoneVerificationModalOpen, phoneVerificationStep]);

  useEffect(() => {
    if (!isPhoneVerificationModalOpen || phoneVerificationStep !== 2) {
      return;
    }
    const isComplete = verificationCodeDigits.every((digit) => digit.length === 1);
    if (!isComplete) {
      if (verificationCodeError) {
        setVerificationCodeError('');
      }
      return;
    }
    const enteredCode = verificationCodeDigits.join('');
    if (enteredCode === SMS_VERIFICATION_CODE) {
      setVerificationCodeError('');
      setIsPhoneVerified(true);
      setPhoneVerificationStep(3);
      notify({ category: 'system', icon: 'check', title: 'Numer telefonu zweryfikowany', body: 'Twój numer telefonu został pomyślnie zweryfikowany.', link: 'accountSecurity', dedupeKey: 'phone-verified' });
      return;
    }
    setVerificationCodeError('Kod jest nieprawidłowy. Spróbuj ponownie.');
  }, [isPhoneVerificationModalOpen, phoneVerificationStep, verificationCodeDigits, verificationCodeError]);

  return (
    <>
    <section className="account-settings-page" aria-label="Edytuj profil">
      <div className="account-layout">
        <AccountSidebar />

        <div className="account-main account-settings-main">
          <div className="settings-breadcrumb">Dane i ustawienia <Icon name="chevron" /> <strong>Edytuj profil</strong></div>
          <header className="settings-head profile-edit-head">
            <h1>Edytuj profil</h1>
            <p>Zarządzaj swoimi danymi osobowymi i wizerunkiem w Mieszko.</p>
          </header>

          <div className="profile-edit-layout">
            <section className="settings-card profile-edit-main-card">
              <div className="settings-card-head">
                <h2>Zdjęcie profilowe</h2>
              </div>

              <div className="profile-photo-row">
                <div className="profile-photo-avatar">{profilePhotoImage ? <img src={profilePhotoImage} alt="Zdjęcie profilowe" /> : getInitials(getDisplayName(user))}</div>
                <div>
                  <small>{profilePhotoName ? `Wybrano: ${profilePhotoName}` : 'JPG, PNG lub WEBP. Maks. 5 MB.'}</small>
                  <div className="profile-photo-actions">
                    <label>
                      <input
                        ref={profilePhotoInputRef}
                        type="file"
                        accept=".jpg,.jpeg,.png,.webp"
                        onChange={(event) => {
                          void handleModeratedUpload(event, 'avatar');
                        }}
                      />
                      {profilePhotoName ? 'Zmień zdjęcie' : 'Dodaj zdjęcie'}
                    </label>
                    {profilePhotoName && <button type="button" onClick={clearProfilePhoto}>Usuń zdjęcie</button>}
                  </div>
                </div>
              </div>

              <div className="settings-separator" />

              <div className="settings-card-head">
                <h2>Dane podstawowe</h2>
              </div>

              <div className="profile-form-grid two-col">
                <label>
                  <span>Imię</span>
                  <input value={profile.firstName} onChange={(event) => setField('firstName', event.target.value)} />
                </label>
                <label>
                  <span>Nazwisko</span>
                  <input value={profile.lastName} onChange={(event) => setField('lastName', event.target.value)} />
                </label>
              </div>

              <div className="profile-form-grid">
                <label>
                  <span>Nazwa użytkownika (nick)</span>
                  <div className="input-action-row">
                    <input value={profile.nick} onChange={(event) => setField('nick', event.target.value)} />
                    <button type="button" onClick={() => setField('nick', '')}>Zmień nick</button>
                  </div>
                  <small>mieszko.pl/u/{profile.nick}</small>
                </label>

                <label>
                  <span>E-mail</span>
                  <input value={profile.email} readOnly disabled />
                </label>

                <label>
                  <span>Numer telefonu</span>
                  <div className={isPhoneVerified ? 'phone-row verified-row' : 'phone-row'}>
                    <div ref={phoneCountryRef} className="phone-prefix phone-country-picker">
                      <button
                        type="button"
                        className="phone-country-trigger"
                        onClick={() => setIsPhoneCountryOpen((current) => !current)}
                        aria-haspopup="listbox"
                        aria-expanded={isPhoneCountryOpen}
                      >
                        <img src={`https://flagcdn.com/w20/${selectedPhoneCountry.iso2}.png`} alt={`Flaga ${selectedPhoneCountry.country}`} loading="lazy" />
                        <span>{selectedPhoneCountry.code}</span>
                        <Icon name="chevron" />
                      </button>
                      {isPhoneCountryOpen && (
                        <div className="phone-country-menu" role="listbox" aria-label="Wybór kierunkowego">
                          {phoneCountryOptions.map((option) => (
                            <button
                              key={`${option.code}-${option.country}`}
                              type="button"
                              className={option.code === profile.phonePrefix ? 'active' : ''}
                              onClick={() => {
                                handlePhoneCountryChange(option.code);
                                setIsPhoneCountryOpen(false);
                              }}
                            >
                              <img src={`https://flagcdn.com/w20/${option.iso2}.png`} alt={`Flaga ${option.country}`} loading="lazy" />
                              <span>{option.code}</span>
                              <small>{option.country} ({option.digits})</small>
                            </button>
                          ))}
                        </div>
                      )}
                    </div>
                    <input
                      className={[
                        'phone-number-input',
                        shouldShowPhoneError ? 'invalid' : '',
                        isPhoneVerified ? 'verified' : '',
                      ].filter(Boolean).join(' ')}
                      value={phoneDisplayValue}
                      inputMode="numeric"
                      maxLength={phoneMaskPlaceholder.length}
                      placeholder={phoneMaskPlaceholder}
                      onBlur={() => setIsPhoneTouched(true)}
                      onChange={(event) => handlePhoneInputChange(event.target.value)}
                    />
                    <div className="phone-action-buttons">
                      <button type="button" onClick={() => {
                        setField('phone', '');
                        setIsPhoneTouched(false);
                        setIsPhoneVerified(false);
                      }}>
                        Zmień telefon
                      </button>
                      {isPhoneNumberComplete && (
                        <button type="button" className={isPhoneVerified ? 'phone-confirm-button phone-confirmed' : 'phone-confirm-button'} onClick={openPhoneVerificationModal}>
                          {isPhoneVerified ? 'Numer zweryfikowany' : 'Potwierdź swój numer telefonu'}
                        </button>
                      )}
                    </div>
                  </div>
                  {shouldShowPhoneError && (
                    <small className="phone-error-message">
                      Nieprawidłowa długość numeru dla kraju {selectedPhoneCountry.country}. Wymagane: {selectedPhoneDigitsRequired} cyfr.
                    </small>
                  )}
                  {!shouldShowPhoneError && isPhoneVerified && <small className="phone-verified-message">Numer zweryfikowany</small>}
                </label>

                <label>
                  <span>Data urodzenia</span>
                  <div className="input-action-row">
                    <input
                      type="date"
                      max={todayIsoDate}
                      value={profile.birthDate}
                      onChange={(event) => setField('birthDate', event.target.value)}
                    />
                    <button type="button" onClick={() => setField('birthDate', '')}>Zmień datę</button>
                  </div>
                </label>

                <label>
                  <span>Biografia</span>
                  <textarea
                    maxLength={160}
                    value={profile.bio}
                    onChange={(event) => setField('bio', event.target.value)}
                  />
                  <small>{profile.bio.length} / 160 znaków</small>
                </label>
              </div>

              <div className="profile-form-actions">
                <button type="button" onClick={saveProfile}>Zapisz zmiany</button>
                {message && <span className={`profile-message ${messageTone}`}>{message}</span>}
              </div>
            </section>

            <aside className="profile-edit-side">
              <section className="settings-card">
                <div className="settings-card-head">
                  <h2>Personalizacja profilu</h2>
                  <p>Dostosuj swój profil do swoich preferencji.</p>
                </div>
                <div className="settings-list compact profile-personalization-list">
                  <article className="cover-photo-row">
                    <span><Icon name="camera" /></span>
                    <em>Zdjęcie w tle profilu</em>
                    <strong>{coverPhotoName ? `Wybrano: ${coverPhotoName}` : 'Dodaj zdjęcie w tle swojego profilu.'}</strong>
                    <label className="mini-tag cover-upload-tag">
                      {coverPhotoName ? 'Zmień zdjęcie' : 'Dodaj zdjęcie'}
                      <input
                        ref={coverPhotoInputRef}
                        type="file"
                        accept=".jpg,.jpeg,.png,.webp"
                        onChange={(event) => {
                          void handleModeratedUpload(event, 'cover');
                        }}
                      />
                    </label>
                  </article>
                  {coverPhotoName && (
                    <div className="cover-photo-actions-row">
                      <Link className="cover-preview-button" to={ROUTES.accountPublicProfile}>Podgląd profilu</Link>
                      <button type="button" className="cover-remove-button" onClick={clearCoverPhoto}>Usuń zdjęcie</button>
                    </div>
                  )}
                  <article className="profile-visibility-card">
                    <span><Icon name="eye" /></span>
                    <em>Widoczność profilu</em>
                    <div className="profile-visibility-options" role="radiogroup" aria-label="Widoczność profilu">
                      <button
                        type="button"
                        role="radio"
                        aria-checked={profileVisibility === 'publiczna'}
                        className={profileVisibility === 'publiczna' ? 'active' : ''}
                        onClick={() => setProfileVisibility('publiczna')}
                      >
                        Publiczna
                      </button>
                      <button
                        type="button"
                        role="radio"
                        aria-checked={profileVisibility === 'prywatna'}
                        className={profileVisibility === 'prywatna' ? 'active' : ''}
                        onClick={() => setProfileVisibility('prywatna')}
                      >
                        Prywatna
                      </button>
                      <button
                        type="button"
                        role="radio"
                        aria-checked={profileVisibility === 'kontakty'}
                        className={profileVisibility === 'kontakty' ? 'active' : ''}
                        onClick={() => setProfileVisibility('kontakty')}
                      >
                        Tylko kontakty
                      </button>
                    </div>
                    <strong>
                      {profileVisibility === 'publiczna' && 'Twój profil jest widoczny dla wszystkich użytkowników.'}
                      {profileVisibility === 'prywatna' && 'Twój profil jest ukryty i widzisz go tylko Ty.'}
                      {profileVisibility === 'kontakty' && 'Profil widzą tylko osoby, z którymi masz aktywny kontakt.'}
                    </strong>
                  </article>
                  <article className="public-profile-row">
                    <span><Icon name="user" /></span>
                    <em>
                      {profileVisibility === 'prywatna' && 'Profil prywatny'}
                      {profileVisibility === 'publiczna' && 'Profil publiczny'}
                      {profileVisibility === 'kontakty' && 'Profil dla kontaktów'}
                    </em>
                    <strong>
                      {profileVisibility === 'prywatna' && 'Twój profil jest widoczny tylko dla Ciebie.'}
                      {profileVisibility === 'publiczna' && 'Zobacz jak inni widzą Twój profil.'}
                      {profileVisibility === 'kontakty' && 'Profil widzą tylko zaakceptowane kontakty.'}
                    </strong>
                    <Link className="mini-tag profile-preview-cta" to={ROUTES.accountPublicProfile}>
                      Zobacz profil <Icon name="arrow" />
                    </Link>
                  </article>
                </div>
              </section>

              <section className="settings-card">
                <div className="settings-card-head">
                  <h2>Dodatkowe informacje</h2>
                  <p>Zarządzaj dodatkowymi informacjami w swoim koncie.</p>
                </div>
                <div className="profile-form-grid profile-extra-form">
                  <label>
                    <span>Adres zamieszkania (opcjonalnie)</span>
                    <input
                      value={profile.address}
                      placeholder="Np. Łódź, ul. Piotrkowska 12"
                      onChange={(event) => setField('address', event.target.value)}
                    />
                  </label>
                  <label>
                    <span>Preferencje kontaktu</span>
                    <select
                      value={profile.contactPreference}
                      onChange={(event) => setField('contactPreference', event.target.value)}
                    >
                      <option value="EMAIL">E-mail</option>
                      <option value="PHONE">Telefon</option>
                      <option value="EMAIL_AND_PHONE">E-mail i telefon</option>
                    </select>
                  </label>
                  <label>
                    <span>Język komunikacji</span>
                    <select
                      value={profile.preferredLanguage}
                      onChange={(event) => setField('preferredLanguage', event.target.value)}
                    >
                      <option value="PL">Polski</option>
                      <option value="EN">Angielski</option>
                      <option value="UK">Ukraiński</option>
                      <option value="DE">Niemiecki</option>
                    </select>
                  </label>
                </div>
              </section>

              <section className="settings-card">
                <div className="settings-card-head">
                  <h2>Usunięcie konta</h2>
                  <p>Jeśli chcesz usunąć swoje konto i wszystkie dane.</p>
                </div>
                <button type="button" className="delete-account-button"><Icon name="warning" /> Usuń konto</button>
              </section>
            </aside>
          </div>

          <footer className="settings-footer-note">
            <Icon name="lock" /> Twoje dane są u nas bezpieczne. Stosujemy szyfrowanie SSL i zaawansowane środki ochrony danych.
          </footer>
        </div>
      </div>
    </section>
    {isPhoneVerificationModalOpen && (
      <div className="phone-verify-modal-overlay" role="dialog" aria-modal="true" aria-label={phoneVerificationStep === 1 ? 'Potwierdzenie numeru telefonu' : phoneVerificationStep === 2 ? 'Wpisz kod SMS' : 'Numer telefonu zweryfikowany'}>
        <div className="phone-verify-modal">
          <button type="button" className="phone-verify-close" onClick={closePhoneVerificationModal}>X</button>

          <div className="phone-verify-progress">
            <span className="active" />
            <span className={phoneVerificationStep >= 2 ? 'active' : ''} />
            <span className={phoneVerificationStep >= 3 ? 'active' : ''} />
          </div>

          {phoneVerificationStep === 1 ? (
            <>
              <div className="phone-verify-illustration" aria-hidden="true">
                <span className="phone-verify-shield"><Icon name="shield" /></span>
                <span className="phone-verify-phone"><Icon name="message" /></span>
              </div>

              <h2>Potwierdź swój numer telefonu</h2>
              <p>Wyślemy do Ciebie kod SMS, aby potwierdzić Twój numer telefonu.</p>

              <div className="phone-verify-number-box">
                <small>Twój numer telefonu</small>
                <div>
                  <strong>{selectedPhoneCountry.code}</strong>
                  <span>{phoneSmsDisplayValue}</span>
                </div>
              </div>

              <article className="phone-verify-note">
                <span><Icon name="lock" /></span>
                <div>
                  <strong>Dlaczego to robimy?</strong>
                  <small>Potwierdzenie numeru zwiększa bezpieczeństwo Twojego konta i pomaga w odzyskaniu dostępu.</small>
                </div>
              </article>

              <button type="button" className="phone-verify-send" onClick={sendVerificationSms}><Icon name="message" /> Wyślij SMS z kodem</button>
              <button type="button" className="phone-verify-change" onClick={closePhoneVerificationModal}>Zmień numer telefonu</button>

              <footer>
                <Icon name="lock" /> Nie udostępniamy Twojego numeru telefonu innym użytkownikom. Koszt SMS zgodny z taryfą Twojego operatora.
              </footer>
            </>
          ) : phoneVerificationStep === 2 ? (
            <>
              <div className="phone-code-hero" aria-hidden="true">
                <div className="phone-code-device">
                  <span className="phone-code-notch" />
                </div>
                <span className="phone-code-badge"><Icon name="message" /></span>
                <span className="phone-code-bubble">123456</span>
              </div>

              <h2>Wpisz kod z SMS-a</h2>
              <p>Wysłaliśmy 6-cyfrowy kod na numer:</p>

              <div className="phone-code-number-line">
                <strong>{selectedPhoneCountry.code} {phoneSmsDisplayValue}</strong>
                <button type="button" onClick={() => setPhoneVerificationStep(1)}>Zmień numer</button>
              </div>

              <div className={`phone-code-inputs ${verificationCodeError ? 'is-error' : ''}`} onPaste={handleVerificationPaste}>
                {verificationCodeDigits.map((digit, index) => (
                  <input
                    key={`verification-digit-${index + 1}`}
                    ref={(element) => {
                      verificationInputRefs.current[index] = element;
                    }}
                    type="text"
                    inputMode="numeric"
                    pattern="[0-9]*"
                    maxLength={1}
                    value={digit}
                    onChange={(event) => handleVerificationDigitChange(index, event.target.value)}
                    onKeyDown={(event) => handleVerificationDigitKeyDown(index, event)}
                    aria-label={`Cyfra kodu ${index + 1}`}
                  />
                ))}
              </div>

              {verificationCodeError && <p className="phone-code-error">{verificationCodeError}</p>}

              <p className="phone-code-timer">
                Kod ważny jeszcze przez <strong>04:{String(Math.max(verificationResendCooldown, 0)).padStart(2, '0')}</strong>
              </p>

              <article className="phone-verify-note phone-code-note">
                <span><Icon name="shield" /></span>
                <div>
                  <strong>Nie udostępniaj kodu nikomu.</strong>
                  <small>Mieszko nigdy nie poprosi Cię o kod SMS.</small>
                </div>
              </article>

              <p className="phone-code-question">Nie otrzymałeś kodu?</p>
              <button type="button" className="phone-code-resend" disabled={verificationResendCooldown > 0} onClick={resendVerificationSms}>
                Wyślij kod ponownie {verificationResendCooldown > 0 ? `(${verificationResendCooldown}s)` : ''}
              </button>
            </>
          ) : (
            <>
              <div className="phone-success-hero" aria-hidden="true">
                <span className="phone-success-decoration phone-success-decoration-left-top" />
                <span className="phone-success-decoration phone-success-decoration-left-bottom" />
                <span className="phone-success-decoration phone-success-decoration-right-top" />
                <span className="phone-success-badge">
                  <span className="phone-success-check" />
                </span>
              </div>

              <h2>Numer telefonu został poprawnie zweryfikowany!</h2>
              <p>Dziękujemy! Twój numer telefonu został pomyślnie potwierdzony.</p>

              <article className="phone-success-number-box">
                <span className="phone-success-number-icon"><Icon name="phone" /></span>
                <div>
                  <small>Zweryfikowany numer</small>
                  <strong>{selectedPhoneCountry.code} {phoneSmsDisplayValue}</strong>
                </div>
              </article>

              <button type="button" className="phone-verify-send" onClick={closePhoneVerificationModal}>Kontynuuj</button>

              <article className="phone-success-note">
                <span><Icon name="shield" /></span>
                <div>
                  <strong>Twój numer telefonu jest bezpieczny.</strong>
                  <small>Wykorzystujemy go tylko do weryfikacji konta i zapewnienia bezpieczeństwa.</small>
                </div>
              </article>
            </>
          )}
        </div>
      </div>
    )}
    </>
  );
}

function AccountPublicProfilePage() {
  const navigate = useNavigate();
  const { user } = useAuth();
  const publicProfileCover = window.localStorage.getItem(accountStorageKey('CoverImage', user)) || heroImage;
  const publicReviews = [
    {
      name: 'Anna Nowak',
      text: 'Bardzo profesjonalne podejście i świetny kontakt. Wszystko przebiegło sprawnie.',
      avatar: '',
    },
    {
      name: 'Michał Wiśniewski',
      text: 'Pan Jan pomógł mi znaleźć idealne mieszkanie i przeprowadził przez cały proces.',
      avatar: '',
    },
    {
      name: 'Katarzyna Zielińska',
      text: 'Polecam współpracę. Pełne zaangażowanie i bardzo dobra komunikacja.',
      avatar: '',
    },
  ];

  const initialsFromName = (fullName: string) => fullName
    .split(' ')
    .filter(Boolean)
    .slice(0, 2)
    .map((chunk) => chunk[0]?.toUpperCase() ?? '')
    .join('');

  const publicListings = [
    {
      image: heroImage,
      imagePosition: 'left center',
      badge: 'Na sprzedaż',
      badgeClass: 'sale',
      price: '748 000 zł',
      title: 'Mieszkanie 3-pokojowe',
      location: 'Warszawa, Mokotów, Stegny',
      area: '62 m²',
      rooms: '3 pokoje',
      floor: '4/8 piętro',
      date: 'Dodane 2 dni temu',
    },
    {
      image: cityImage,
      imagePosition: 'center center',
      badge: 'Na sprzedaż',
      badgeClass: 'sale',
      price: '699 000 zł',
      title: 'Mieszkanie 2-pokojowe',
      location: 'Warszawa, Praga-Południe',
      area: '45 m²',
      rooms: '2 pokoje',
      floor: '3/6 piętro',
      date: 'Dodane 5 dni temu',
    },
    {
      image: heroImage,
      imagePosition: '70% center',
      badge: 'Na wynajem',
      badgeClass: 'rent',
      price: '3 200 zł / mies.',
      title: 'Mieszkanie 2-pokojowe',
      location: 'Warszawa, Wola, Odolany',
      area: '48 m²',
      rooms: '2 pokoje',
      floor: '2/7 piętro',
      date: 'Dodane 1 tydzień temu',
    },
  ];

  return (
    <section className="account-settings-page public-profile-page" aria-label="Jak inni widzą Twój profil">
      <div className="account-layout">
        <AccountSidebar />

        <div className="account-main account-settings-main">
          <div className="settings-breadcrumb">Dane i ustawienia <Icon name="chevron" /> <strong>Jak inni widzą Twój profil</strong></div>
          <header className="settings-head public-profile-head">
            <div>
              <h1>Jak inni widzą Twój profil</h1>
              <p>Zobacz, jak Twój profil prezentuje się publicznie dla innych użytkowników Mieszko.</p>
            </div>
            <Link to={ROUTES.accountProfileEdit}><Icon name="gear" /> Edytuj profil</Link>
          </header>

          <section className="public-profile-cover-card">
            <div className="public-profile-cover" style={{ backgroundImage: `url(${publicProfileCover})`, backgroundPosition: 'center center' }} />
            <div className="public-profile-summary">
              <div className="public-profile-avatar">{initialsFromName(getDisplayName(user))}</div>
              <div className="public-profile-user">
                <h2>{getDisplayName(user)}</h2>
                <div className="public-profile-meta">
                  <span><Icon name="calendar" /> Dołączył w marcu 2024</span>
                  <span><Icon name="clock" /> Ostatnio aktywny dziś</span>
                </div>
                <div className="public-profile-rating">
                  <strong>4,9</strong>
                  <span><Icon name="star" /><Icon name="star" /><Icon name="star" /><Icon name="star" /><Icon name="star" /></span>
                  <small>(27 opinii)</small>
                </div>
              </div>
              <blockquote>"Pomagam znaleźć idealne miejsce do życia w najlepszej cenie."</blockquote>
              <div className="public-profile-stats">
                <div><strong>23</strong><small>Ogłoszenia</small></div>
                <div><strong>18</strong><small>Sprzedane</small></div>
                <div><strong>11</strong><small>Wynajęte</small></div>
              </div>
            </div>
          </section>

          <div className="public-profile-grid">
            <section className="settings-card public-profile-listings">
              <div className="settings-card-head">
                <h2>Moje ogłoszenia (23)</h2>
                <button type="button" className="ghost">Zobacz wszystkie</button>
              </div>
              <div className="public-listing-grid">
                {publicListings.map((listing) => (
                  <article className="public-listing-card" key={`${listing.title}-${listing.location}`}>
                    <div className="public-listing-photo" style={{ backgroundImage: `url(${listing.image})`, backgroundPosition: listing.imagePosition }}>
                      <span className={`public-listing-badge ${listing.badgeClass}`}>{listing.badge}</span>
                    </div>
                    <div className="public-listing-body">
                      <strong>{listing.price}</strong>
                      <h3>{listing.title}</h3>
                      <small><Icon name="pin" /> {listing.location}</small>
                      <div className="public-listing-meta">
                        <span><Icon name="scale" /> {listing.area}</span>
                        <span><Icon name="user" /> {listing.rooms}</span>
                        <span><Icon name="building" /> {listing.floor}</span>
                      </div>
                      <div className="public-listing-foot">
                        <em>{listing.date}</em>
                        <button type="button" aria-label="Dodaj do ulubionych"><Icon name="heart" /></button>
                      </div>
                    </div>
                  </article>
                ))}
              </div>
            </section>

            <section className="settings-card public-profile-reviews">
              <div className="settings-card-head">
                <h2>Opinie o mnie</h2>
                <button type="button" className="ghost">Zobacz wszystkie</button>
              </div>
              <div className="public-review-list">
                {publicReviews.map((review) => (
                  <article className="public-review-item" key={review.name}>
                    <div className="public-review-avatar" aria-hidden="true">
                      {review.avatar ? <img src={review.avatar} alt="" /> : <span>{initialsFromName(review.name)}</span>}
                    </div>
                    <div className="public-review-content">
                      <strong>{review.name}</strong>
                      <div className="public-review-stars"><Icon name="star" /><Icon name="star" /><Icon name="star" /><Icon name="star" /><Icon name="star" /></div>
                      <p>{review.text}</p>
                    </div>
                  </article>
                ))}
              </div>
            </section>
          </div>

          <section className="settings-card public-profile-about">
            <h2>Kilka słów o mnie</h2>
            <p>Od 5 lat pomagam klientom w sprzedaży, zakupie i wynajmie nieruchomości na terenie Warszawy. Stawiam na uczciwość, transparentność i indywidualne podejście do każdej transakcji.</p>
            <div className="public-profile-badges">
              <span><Icon name="message" /> Szybki kontakt</span>
              <span><Icon name="shield" /> Zweryfikowany użytkownik</span>
              <span><Icon name="check" /> Bezpieczne transakcje</span>
            </div>
          </section>

          <footer className="settings-footer-note">
            <Icon name="lock" /> To jest podgląd Twojego publicznego profilu. Dane kontaktowe (e-mail, telefon) nie są widoczne dla innych użytkowników.
          </footer>
        </div>
      </div>
    </section>
  );
}

type LoginStep = 'login' | 'register-choice' | 'register-private' | 'register-company' | 'register-pending';

function LoginPage({ onLogin }: { onLogin: (user: AuthUser) => void }) {
  const { login, register, socialLogin } = useAuth();
  const [step, setStep] = useState<LoginStep>('login');
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [confirmPassword, setConfirmPassword] = useState('');
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  const [phone, setPhone] = useState('');
  const [companyName, setCompanyName] = useState('');
  const [nip, setNip] = useState('');
  const [companyPhone, setCompanyPhone] = useState('');
  const [agreeTerms, setAgreeTerms] = useState(false);
  const [showPassword, setShowPassword] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [submitting, setSubmitting] = useState(false);
  const [pendingUser, setPendingUser] = useState<AuthUser | null>(null);

  const runAuth = async (action: () => Promise<AuthUser>) => {
    if (submitting) {
      return;
    }
    setError(null);
    setSubmitting(true);
    try {
      const authedUser = await action();
      onLogin(authedUser);
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Nie udało się zalogować. Spróbuj ponownie.');
    } finally {
      setSubmitting(false);
    }
  };

  const runRegister = async (action: () => Promise<AuthUser>) => {
    if (submitting) {
      return;
    }
    setError(null);
    setSubmitting(true);
    try {
      const registeredUser = await action();
      setPendingUser(registeredUser);
      setStep('register-pending');
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Nie udało się utworzyć konta. Spróbuj ponownie.');
    } finally {
      setSubmitting(false);
    }
  };

  const goToStep = (nextStep: LoginStep) => {
    setStep(nextStep);
    setError(null);
  };

  const handleLoginSubmit = (event: ReactFormEvent<HTMLFormElement>) => {
    event.preventDefault();
    runAuth(() => login(email.trim(), password));
  };

  const validateRegisterPasswords = () => {
    if (password !== confirmPassword) {
      setError('Hasła nie są identyczne.');
      return false;
    }
    if (!agreeTerms) {
      setError('Musisz zaakceptować regulamin oraz politykę prywatności.');
      return false;
    }
    return true;
  };

  const handlePrivateRegisterSubmit = (event: ReactFormEvent<HTMLFormElement>) => {
    event.preventDefault();
    if (!validateRegisterPasswords()) {
      return;
    }
    const fullName = `${firstName.trim()} ${lastName.trim()}`.trim();
    runRegister(() => register(email.trim(), password, fullName, {
      accountType: 'PERSONAL',
      phone: phone.trim() || undefined,
    }));
  };

  const handleCompanyRegisterSubmit = (event: ReactFormEvent<HTMLFormElement>) => {
    event.preventDefault();
    if (!validateRegisterPasswords()) {
      return;
    }
    runRegister(() => register(email.trim(), password, companyName.trim(), {
      accountType: 'COMPANY',
      nip: nip.trim() || undefined,
    }));
  };

  const socialButtons = (
    <>
      <div className="login-divider">
        <span>lub</span>
      </div>

      <button
        className="social-login"
        type="button"
        disabled={submitting}
        onClick={() => runAuth(() => socialLogin('GOOGLE'))}
      >
        <span className="google-mark">G</span>
        {step === 'login' ? 'Kontynuuj przez Google' : 'Zarejestruj się przez Google'}
      </button>
      <button
        className="social-login"
        type="button"
        disabled={submitting}
        onClick={() => runAuth(() => socialLogin('FACEBOOK'))}
      >
        <span className="facebook-mark">f</span>
        {step === 'login' ? 'Kontynuuj przez Facebook' : 'Zarejestruj się przez Facebook'}
      </button>

      <p className="login-security-note"><Icon name="lock" /> Twoje dane są u nas bezpieczne. Stosujemy szyfrowanie SSL.</p>
    </>
  );

  return (
    <section className="login-page" aria-label="Logowanie">
      <div className="login-stage">
        <div className="login-left">
          <div className="login-intro">
            <h1>Witaj ponownie!</h1>
            <p>Zaloguj się, aby zarządzać ogłoszeniami, zapisywać ulubione oferty i korzystać ze wszystkich funkcji Mieszko.</p>
          </div>

          <div className="login-benefits">
            <article>
              <span><Icon name="heart" /></span>
              <p>
                <strong>Zapisuj ulubione</strong>
                <small>Twórz listy ulubionych mieszkań i wracaj do nich w każdej chwili.</small>
              </p>
            </article>
            <article>
              <span><Icon name="chart" /></span>
              <p>
                <strong>Śledź zmiany cen</strong>
                <small>Otrzymuj powiadomienia o zmianach cen mieszkań w interesujących Cię lokalizacjach.</small>
              </p>
            </article>
            <article>
              <span><Icon name="document" /></span>
              <p>
                <strong>Zarządzaj ogłoszeniami</strong>
                <small>Edytuj, promuj i zarządzaj swoimi ogłoszeniami w jednym miejscu.</small>
              </p>
            </article>
            <article>
              <span><Icon name="shield" /></span>
              <p>
                <strong>Pełna personalizacja</strong>
                <small>Dostosuj ustawienia konta i otrzymuj spersonalizowane rekomendacje.</small>
              </p>
            </article>
          </div>

          <div className="login-room-photo" style={{ backgroundImage: `url(${loginRoomImage})` }} aria-hidden="true" />
        </div>

        <div className="login-right">
          {step === 'login' && (
            <form className="login-card" onSubmit={handleLoginSubmit}>
              <div className="login-card-head">
                <h2>Zaloguj się do swojego konta</h2>
                <p>
                  Nie masz jeszcze konta?{' '}
                  <button type="button" onClick={() => goToStep('register-choice')}>Zarejestruj się</button>
                </p>
              </div>

              <label className="login-field">
                <span>Adres e-mail</span>
                <div>
                  <input
                    type="email"
                    placeholder="Wpisz swój adres e-mail"
                    value={email}
                    onChange={(event) => setEmail(event.target.value)}
                    autoComplete="email"
                    required
                  />
                  <Icon name="mail" />
                </div>
              </label>

              <label className="login-field">
                <span>Hasło</span>
                <div>
                  <input
                    type={showPassword ? 'text' : 'password'}
                    placeholder="Wpisz swoje hasło"
                    value={password}
                    onChange={(event) => setPassword(event.target.value)}
                    autoComplete="current-password"
                    minLength={6}
                    required
                  />
                  <button
                    type="button"
                    className="login-eye"
                    aria-label={showPassword ? 'Ukryj hasło' : 'Pokaż hasło'}
                    onClick={() => setShowPassword((current) => !current)}
                  >
                    <Icon name="eye" />
                  </button>
                </div>
              </label>

              {error && <p className="login-error" role="alert">{error}</p>}

              <div className="login-options-row">
                <label className="remember-me">
                  <input type="checkbox" />
                  <span>Zapamiętaj mnie</span>
                </label>
                <button className="forgot-password" type="button">Nie pamiętasz hasła?</button>
              </div>

              <button className="login-submit" type="submit" disabled={submitting}>
                {submitting ? 'Przetwarzanie…' : 'Zaloguj się'}
              </button>

              {socialButtons}
            </form>
          )}

          {step === 'register-choice' && (
            <div className="login-card register-choice-card">
              <div className="login-card-head">
                <h2>Dla kogo tworzysz konto?</h2>
                <p className="register-choice-subtitle">Wybierz opcję, która najlepiej Cię opisuje.</p>
              </div>

              <div className="register-choice-list">
                <button type="button" className="register-choice-option" onClick={() => goToStep('register-private')}>
                  <span><Icon name="user" /></span>
                  <span className="register-choice-copy">
                    <strong>Jestem osobą prywatną</strong>
                    <small>Szukam, sprzedaję lub wynajmuję jako osoba prywatna</small>
                  </span>
                  <Icon name="arrow" />
                </button>
                <button type="button" className="register-choice-option" onClick={() => goToStep('register-company')}>
                  <span><Icon name="building" /></span>
                  <span className="register-choice-copy">
                    <strong>Jestem firmą</strong>
                    <small>Działam w imieniu firmy lub zarządzam nieruchomościami komercyjnymi</small>
                  </span>
                  <Icon name="arrow" />
                </button>
              </div>

              <p className="register-choice-footer">
                Masz już konto?{' '}
                <button type="button" onClick={() => goToStep('login')}>Zaloguj się</button>
              </p>
            </div>
          )}

          {step === 'register-private' && (
            <form className="login-card" onSubmit={handlePrivateRegisterSubmit}>
              <div className="login-card-head">
                <h2>Utwórz swoje konto</h2>
                <p>
                  Masz już konto?{' '}
                  <button type="button" onClick={() => goToStep('login')}>Zaloguj się</button>
                </p>
              </div>

              <div className="register-type-tabs" role="tablist">
                <button type="button" role="tab" aria-selected="true" className="active">
                  <Icon name="user" /> Jestem osobą prywatną
                </button>
                <button type="button" role="tab" aria-selected="false" onClick={() => goToStep('register-company')}>
                  <Icon name="building" /> Jestem firmą
                </button>
              </div>

              <div className="login-field-row">
                <label className="login-field">
                  <span>Imię</span>
                  <div>
                    <input
                      type="text"
                      placeholder="Wpisz swoje imię"
                      value={firstName}
                      onChange={(event) => setFirstName(event.target.value)}
                      autoComplete="given-name"
                      required
                    />
                    <Icon name="user" />
                  </div>
                </label>
                <label className="login-field">
                  <span>Nazwisko</span>
                  <div>
                    <input
                      type="text"
                      placeholder="Wpisz swoje nazwisko"
                      value={lastName}
                      onChange={(event) => setLastName(event.target.value)}
                      autoComplete="family-name"
                      required
                    />
                    <Icon name="user" />
                  </div>
                </label>
              </div>

              <label className="login-field">
                <span>Adres e-mail</span>
                <div>
                  <input
                    type="email"
                    placeholder="Wpisz swój adres e-mail"
                    value={email}
                    onChange={(event) => setEmail(event.target.value)}
                    autoComplete="email"
                    required
                  />
                  <Icon name="mail" />
                </div>
              </label>

              <div className="login-field-row">
                <label className="login-field">
                  <span>Hasło</span>
                  <div>
                    <input
                      type={showPassword ? 'text' : 'password'}
                      placeholder="Utwórz hasło"
                      value={password}
                      onChange={(event) => setPassword(event.target.value)}
                      autoComplete="new-password"
                      minLength={6}
                      required
                    />
                    <button
                      type="button"
                      className="login-eye"
                      aria-label={showPassword ? 'Ukryj hasło' : 'Pokaż hasło'}
                      onClick={() => setShowPassword((current) => !current)}
                    >
                      <Icon name="eye" />
                    </button>
                  </div>
                </label>
                <label className="login-field">
                  <span>Powtórz hasło</span>
                  <div>
                    <input
                      type={showPassword ? 'text' : 'password'}
                      placeholder="Powtórz swoje hasło"
                      value={confirmPassword}
                      onChange={(event) => setConfirmPassword(event.target.value)}
                      autoComplete="new-password"
                      minLength={6}
                      required
                    />
                    <Icon name="eye" />
                  </div>
                </label>
              </div>

              <label className="login-field">
                <span>Numer telefonu (opcjonalnie)</span>
                <div>
                  <input
                    type="tel"
                    placeholder="Wpisz swój numer telefonu"
                    value={phone}
                    onChange={(event) => setPhone(event.target.value)}
                    autoComplete="tel"
                  />
                  <Icon name="phone" />
                </div>
              </label>

              <label className="terms-checkbox">
                <input type="checkbox" checked={agreeTerms} onChange={(event) => setAgreeTerms(event.target.checked)} required />
                <span>Akceptuję regulamin oraz politykę prywatności</span>
              </label>

              {error && <p className="login-error" role="alert">{error}</p>}

              <button className="login-submit" type="submit" disabled={submitting}>
                {submitting ? 'Przetwarzanie…' : 'Zarejestruj się'}
              </button>

              {socialButtons}
            </form>
          )}

          {step === 'register-company' && (
            <form className="login-card" onSubmit={handleCompanyRegisterSubmit}>
              <div className="login-card-head">
                <h2>Utwórz konto firmowe</h2>
                <p>
                  Masz już konto?{' '}
                  <button type="button" onClick={() => goToStep('login')}>Zaloguj się</button>
                </p>
              </div>

              <div className="register-type-tabs" role="tablist">
                <button type="button" role="tab" aria-selected="false" onClick={() => goToStep('register-private')}>
                  <Icon name="user" /> Jestem osobą prywatną
                </button>
                <button type="button" role="tab" aria-selected="true" className="active">
                  <Icon name="building" /> Jestem firmą
                </button>
              </div>

              <div className="login-field-row">
                <label className="login-field">
                  <span>Nazwa firmy</span>
                  <div>
                    <input
                      type="text"
                      placeholder="Wpisz nazwę firmy"
                      value={companyName}
                      onChange={(event) => setCompanyName(event.target.value)}
                      autoComplete="organization"
                      required
                    />
                    <Icon name="building" />
                  </div>
                </label>
                <label className="login-field">
                  <span>NIP (opcjonalnie)</span>
                  <div>
                    <input
                      type="text"
                      placeholder="123-456-78-90"
                      value={nip}
                      onChange={(event) => setNip(event.target.value)}
                    />
                    <Icon name="document" />
                  </div>
                </label>
              </div>

              <label className="login-field">
                <span>Adres e-mail firmowy</span>
                <div>
                  <input
                    type="email"
                    placeholder="Wpisz adres e-mail firmowy"
                    value={email}
                    onChange={(event) => setEmail(event.target.value)}
                    autoComplete="email"
                    required
                  />
                  <Icon name="mail" />
                </div>
              </label>

              <label className="login-field">
                <span>Numer telefonu (opcjonalnie)</span>
                <div>
                  <input
                    type="tel"
                    placeholder="Wpisz numer telefonu firmowego"
                    value={companyPhone}
                    onChange={(event) => setCompanyPhone(event.target.value)}
                    autoComplete="tel"
                  />
                  <Icon name="phone" />
                </div>
              </label>

              <div className="login-field-row">
                <label className="login-field">
                  <span>Hasło</span>
                  <div>
                    <input
                      type={showPassword ? 'text' : 'password'}
                      placeholder="Utwórz hasło"
                      value={password}
                      onChange={(event) => setPassword(event.target.value)}
                      autoComplete="new-password"
                      minLength={6}
                      required
                    />
                    <button
                      type="button"
                      className="login-eye"
                      aria-label={showPassword ? 'Ukryj hasło' : 'Pokaż hasło'}
                      onClick={() => setShowPassword((current) => !current)}
                    >
                      <Icon name="eye" />
                    </button>
                  </div>
                </label>
                <label className="login-field">
                  <span>Powtórz hasło</span>
                  <div>
                    <input
                      type={showPassword ? 'text' : 'password'}
                      placeholder="Powtórz swoje hasło"
                      value={confirmPassword}
                      onChange={(event) => setConfirmPassword(event.target.value)}
                      autoComplete="new-password"
                      minLength={6}
                      required
                    />
                    <Icon name="eye" />
                  </div>
                </label>
              </div>

              <label className="terms-checkbox">
                <input type="checkbox" checked={agreeTerms} onChange={(event) => setAgreeTerms(event.target.checked)} required />
                <span>Akceptuję regulamin oraz politykę prywatności</span>
              </label>

              {error && <p className="login-error" role="alert">{error}</p>}

              <button className="login-submit" type="submit" disabled={submitting}>
                {submitting ? 'Przetwarzanie…' : 'Zarejestruj firmę'}
              </button>

              {socialButtons}
            </form>
          )}

          {step === 'register-pending' && (
            <div className="login-card register-pending-card">
              <span className="register-pending-icon"><Icon name="clock" /></span>
              <h2>Dziękujemy za rejestrację!</h2>
              <p className="register-pending-lead">
                Twoje konto zostało utworzone i <strong>oczekuje na weryfikację</strong> przez administratora.
              </p>
              <p className="register-pending-note">
                Trwa weryfikacja konta, aby zapewnić bezpieczeństwo i wysoką jakość ogłoszeń w serwisie. Zwykle zajmuje to tylko chwilę. Możesz już korzystać z serwisu – pełny dostęp zostanie odblokowany po weryfikacji.
              </p>
              <button
                type="button"
                className="login-submit"
                onClick={() => pendingUser && onLogin(pendingUser)}
              >
                Przejdź do swojego konta
              </button>
            </div>
          )}
        </div>

        <div className="login-trust">
          <article>
            <span><Icon name="shield" /></span>
            <p>
              <strong>Bezpieczne dane</strong>
              <small>Twoje dane są chronione najwyższymi standardami bezpieczeństwa.</small>
            </p>
          </article>
          <article>
            <span><Icon name="lock" /></span>
            <p>
              <strong>Brak spamu</strong>
              <small>Nie wysyłamy spamu. Kontrolujesz swoje powiadomienia.</small>
            </p>
          </article>
          <article>
            <span><Icon name="phone" /></span>
            <p>
              <strong>Pomoc i wsparcie</strong>
              <small>Nasz zespół jest gotowy pomóc Ci na każdym etapie.</small>
            </p>
          </article>
          <article>
            <span><Icon name="check" /></span>
            <p>
              <strong>Zaufany serwis</strong>
              <small>Tysiące użytkowników ufa nam w znalezieniu idealnego mieszkania.</small>
            </p>
          </article>
        </div>
      </div>
    </section>
  );
}

function HomePage({ filters, onOpenMap }: { filters: FilterState; onOpenMap: (city: string, mode?: 'buy' | 'rent') => void }) {
  const navigate = useNavigate();
  const [favoriteListings, setFavoriteListings] = useState<Set<string>>(new Set());

  const toggleFavoriteListing = (location: string) => {
    setFavoriteListings((current) => {
      const next = new Set(current);
      if (next.has(location)) {
        next.delete(location);
      } else {
        next.add(location);
      }
      return next;
    });
  };

  return (
    <>
      <section className="hero" aria-label="Sprzedaż mieszkania">
        <img className="hero-photo" src={heroImage} alt="" />
        <div className="hero-gradient" />

        <div className="hero-inner">
          <div className="hero-copy">
            <h1>
              Sprzedaj mieszkanie
              <span>za darmo.</span>
            </h1>
            <p>Bez prowizji. Bez ukrytych opłat. Znajdź kupca szybciej z pomocą AI.</p>

            <div className="benefits" aria-label="Korzyści">
              {benefits.map((benefit) => (
                <div className="benefit" key={benefit.title}>
                  <span className={`benefit-icon ${benefit.icon}`}>
                    <Icon name={benefit.icon} />
                  </span>
                  <span>
                    <strong>{benefit.title}</strong>
                    <small>{benefit.text}</small>
                  </span>
                </div>
              ))}
            </div>
          </div>

          <aside className="ai-card" aria-label="AI dopasowanie">
            <div className="ai-label">
              <Icon name="spark" />
              <span>AI Dopasowanie</span>
            </div>
            <strong>57 osób szuka podobnego mieszkania</strong>
            <div className="avatar-row">
              {avatars.map((avatar) => (
                <span key={avatar}>{avatar}</span>
              ))}
              <em>+53</em>
            </div>
            <button>Zobacz kupujących</button>
          </aside>

          <aside className="tour-card" aria-label="Wirtualny spacer">
            <span className="tour-badge">360°</span>
            <span>
              <strong>Wirtualny spacer</strong>
              <small>Zobacz mieszkanie w 3D</small>
            </span>
          </aside>
        </div>
      </section>

      <section className="search-wrap" aria-label="Wyszukiwarka">
        <div className="search-tabs">
          <Link className="active" to={ROUTES.buy}>
            Kup
          </Link>
          <Link to={ROUTES.rent}>Wynajem</Link>
        </div>

        <div className="search-panel search-panel-mega">
          <MegaSearch filters={filters} onOpenMap={onOpenMap} onSearch={() => navigate(ROUTES.buy)} />
        </div>
      </section>

      <section className="content-shell">
        <div className="home-feature-grid">
          <div className="home-left-stack">
            <div className="tool-grid" aria-label="Narzędzia">
              {insightCards.map((card) => (
                <article className="tool-card" key={card.title}>
                  <span className={`tool-icon ${card.icon}`}>
                    <Icon name={card.icon} />
                  </span>
                  <div>
                    <h2>{card.title}</h2>
                    <p>{card.text}</p>
                  </div>
                  <Link to={card.path}>{card.action}</Link>
                </article>
              ))}
            </div>

            <section className="offers" aria-label="Polecane ogłoszenia">
              <div className="section-head">
                <h2>Polecane ogłoszenia</h2>
                <Link to={ROUTES.buy}>Zobacz wszystkie <Icon name="arrow" /></Link>
              </div>

              <div className="listing-loop">
                <div className="listing-grid">
                  {[...listings, ...listings].map((listing, index) => (
                  <article className="listing-card" key={`${listing.location}-${index}`} aria-hidden={index >= listings.length}>
                    <div className="property-media" style={{ backgroundImage: `url(${heroImage})` }}>
                      {listing.badge && <span className={listing.badge === 'Premium' ? 'dark' : ''}>{listing.badge}</span>}
                      <button
                        aria-label={favoriteListings.has(listing.location) ? 'Usuń z ulubionych' : 'Dodaj do ulubionych'}
                        aria-pressed={favoriteListings.has(listing.location)}
                        className={`favorite-button ${favoriteListings.has(listing.location) ? 'saved' : ''}`}
                        tabIndex={index >= listings.length ? -1 : undefined}
                        type="button"
                        onClick={() => toggleFavoriteListing(listing.location)}
                      >
                        <Icon name="heart" />
                      </button>
                    </div>
                    <div className="listing-body">
                      <p>{listing.location}</p>
                      <small>{listing.meta}</small>
                      <div className="price-row">
                        <strong>{listing.price}</strong>
                        <span>{listing.perMeter}</span>
                      </div>
                    </div>
                  </article>
                  ))}
                </div>
              </div>
            </section>
          </div>

          <div className="home-right-stack">
            <article className="free-card">
              <div>
                <h2>Dodaj ogłoszenie za darmo</h2>
                <p>Dotrzyj do tysięcy potencjalnych kupujących. Bez prowizji. Bez zobowiązań.</p>
                <button>
                  <span>Dodaj ogłoszenie</span>
                  <strong>0 zł</strong>
                </button>
                <small>Zajmie Ci to tylko 2 minuty!</small>
              </div>
              <div className="house-graphic" aria-hidden="true">
                <span className="cloud" />
                <span className="home one" />
                <span className="home two" />
              </div>
            </article>

            <section className="cities" aria-label="Przeglądaj miasta">
              <h2>Przeglądaj miasta</h2>
              <div className="city-grid">
                {cities.map((city) => (
                  <article
                    className="city-card"
                    key={city.name}
                    style={{
                      backgroundImage: `linear-gradient(180deg, rgba(10, 21, 39, 0.1), rgba(10, 21, 39, 0.78)), url(${cityImage})`,
                      backgroundPosition: city.position,
                    }}
                  >
                    <strong>{city.name}</strong>
                    <span>{city.count}</span>
                  </article>
                ))}
                <article className="city-card all-cities">
                  <span>Zobacz wszystkie miasta</span>
                  <strong>500+ lokalizacji</strong>
                  <button aria-label="Zobacz wszystkie miasta">
                    <Icon name="arrow" />
                  </button>
                </article>
              </div>
            </section>
          </div>
        </div>
      </section>
    </>
  );
}

function RentPage({
  filters,
  onOpenMap,
  onOpenListing,
  isFavorite,
  onToggleFavorite,
  savedSearchActive,
  onToggleSavedSearch,
}: {
  filters: FilterState;
  onOpenMap: (city: string, mode?: 'buy' | 'rent') => void;
  onOpenListing: (id: number) => void;
  isFavorite: (listingId: number) => boolean;
  onToggleFavorite: (listing: ApiListingSummary) => void;
  savedSearchActive: boolean;
  onToggleSavedSearch: () => void;
}) {
  const { city, propertyType, pMin, pMax, aMin, aMax, rooms, wtorny, pierwotny, chips, resetAll } = filters;
  const [sort, setSort] = useState<SortKey>('default');
  const [sortOpen, setSortOpen] = useState(false);
  const [studentOnly, setStudentOnly] = useState(false);
  const [petsOnly, setPetsOnly] = useState(false);
  const [parkingOnly, setParkingOnly] = useState(false);
  const sortRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!sortOpen) {
      return;
    }
    const handler = (event: MouseEvent) => {
      if (sortRef.current && !sortRef.current.contains(event.target as Node)) {
        setSortOpen(false);
      }
    };
    document.addEventListener('mousedown', handler);
    return () => document.removeEventListener('mousedown', handler);
  }, [sortOpen]);

  const cityFilter = city.trim();
  const { items: liveRentListings } = useLiveListings('RENT');
  const rentMapCenter = useResolvedCityCenter(city, CITY_COORDS.Warszawa);
  const rentGeoMarkers = useMemo(() => {
    const filtered = cityFilter
      ? liveRentListings.filter((item) => cityStartsWith(item.city, cityFilter))
      : liveRentListings;
    const source = cityFilter ? filtered : liveRentListings;
    return source.slice(0, 12).map((item, index) => {
      const point = item.lat != null && item.lng != null ? clampCenter({ lat: item.lat, lng: item.lng }) : listingLatLng(item, index);
      return {
        id: `rent-${item.id}`,
        label: `${item.price} zł`,
        lat: point.lat,
        lng: point.lng,
      };
    });
  }, [cityFilter, liveRentListings]);

  const quickActionsByTitle: Record<string, () => void> = {
    'Nowe dzisiaj': () => setSort('new'),
    'Dla studentów': () => setStudentOnly((value) => !value),
    'Zwierzęta mile widziane': () => setPetsOnly((value) => !value),
    'Z miejscem parkingowym': () => setParkingOnly((value) => !value),
  };

  const extraChips: { label: string; onRemove: () => void }[] = [
    ...(studentOnly ? [{ label: 'Dla studentów', onRemove: () => setStudentOnly(false) }] : []),
    ...(petsOnly ? [{ label: 'Zwierzęta mile widziane', onRemove: () => setPetsOnly(false) }] : []),
    ...(parkingOnly ? [{ label: 'Z miejscem parkingowym', onRemove: () => setParkingOnly(false) }] : []),
  ];

  const allChips = [...chips, ...extraChips];

  return (
    <section className="buy-page rent-page" aria-label="Wynajem mieszkania">
      <div className="buy-container">
        <div className="breadcrumb">
          <span>Strona główna</span>
          <Icon name="chevron" />
          <strong>Wynajem</strong>
        </div>

        <div className="buy-hero rent-hero">
          <div>
            <h1>
              Wynajem <span>bez prowizji</span>
            </h1>
            <p>Znajdź mieszkanie lub dom na wynajem w całej Polsce.</p>
          </div>
        </div>

        <div className="buy-search-row">
          <MegaSearch filters={filters} onOpenMap={onOpenMap} mode="rent" />

          <button
            className={`save-search ${savedSearchActive ? 'saved' : ''}`}
            type="button"
            aria-pressed={savedSearchActive}
            onClick={onToggleSavedSearch}
          >
            <Icon name="bell" />
            <span>{savedSearchActive ? 'Zapisano wyszukiwanie' : 'Zapisz wyszukiwanie'}</span>
          </button>
        </div>

        <div className="quick-filter-row rent-quick-row">
          {rentQuickFilters.map((filter) => (
            <button
              className="quick-filter"
              key={filter.title}
              type="button"
              onClick={quickActionsByTitle[filter.title]}
            >
              <Icon name={filter.icon} />
              <span>
                <strong>{filter.title}</strong>
                <small>{filter.text}</small>
              </span>
            </button>
          ))}
        </div>

        <section className="buy-results-zone" aria-label="Wyniki wyszukiwania najmu">
          <div className="buy-layout buy-layout-results">
          <section className="results-column" aria-label="Lista mieszkań na wynajem">
            <div className="results-head">
              <h2>Oferty wynajmu</h2>
              {allChips.map((chip) => (
                <button className="filter-chip" type="button" key={chip.label} onClick={chip.onRemove}>
                  {chip.label} ×
                </button>
              ))}
            </div>

            <RealListingsBand
              offerType="RENT"
              onOpenListing={onOpenListing}
              city={city}
              isFavorite={isFavorite}
              onToggleFavorite={onToggleFavorite}
            />

            <div className="result-list" />
          </section>

          <aside className="map-column" aria-label="Mapa ofert">
            <div className="buy-toolbar">
              <div className="toolbar-actions">
                <div className={`sort-select ${sortOpen ? 'open' : ''}`} ref={sortRef}>
                  <span className="sort-label">Sortuj:</span>
                  <button type="button" className="sort-trigger" onClick={() => setSortOpen((open) => !open)}>
                    <span>{SORT_OPTIONS.find((option) => option.value === sort)?.label}</span>
                    <Icon name="chevron" />
                  </button>
                  {sortOpen && (
                    <div className="sort-menu">
                      {SORT_OPTIONS.map((option) => (
                        <button
                          type="button"
                          key={option.value}
                          className={option.value === sort ? 'active' : ''}
                          onClick={() => { setSort(option.value); setSortOpen(false); }}
                        >
                          <span>{option.label}</span>
                          {option.value === sort && <Icon name="check" />}
                        </button>
                      ))}
                    </div>
                  )}
                </div>
                <div className="view-toggle">
                  <button className="active" type="button">
                    <Icon name="list" />
                    Lista
                  </button>
                  <button type="button" onClick={() => onOpenMap(city, 'rent')}>
                    <Icon name="pin" />
                    Mapa
                  </button>
                </div>
              </div>
            </div>
            <div className="map-card">
              <SatelliteMap
                markers={rentMapMarkers}
                center={rentMapCenter}
                cityLabel={city || 'Warszawa'}
                geoMarkers={rentGeoMarkers}
                onMapClick={() => onOpenMap(city, 'rent')}
              />
            </div>

            <div className="map-ai-card">
              <div className="ai-label">
                <Icon name="spark" />
                <span>AI Dopasowanie</span>
              </div>
              <p>Powiedz nam, czego szukasz, a AI znajdzie dla Ciebie idealne oferty wynajmu.</p>
              <button>
                <Icon name="spark" />
                Dopasuj mi mieszkanie
              </button>
            </div>
          </aside>
          </div>
        </section>
      </div>
    </section>
  );
}

function AddListingPage() {
  const navigate = useNavigate();
  const [currentStep, setCurrentStep] = useState(0);
  const [listingDetails, setListingDetails] = useState(INITIAL_LISTING_DETAILS);
  const [location, setLocation] = useState<ListingLocation>(INITIAL_LISTING_LOCATION);
  const [photos, setPhotos] = useState<ListingPhoto[]>([]);
  const [extraInfo, setExtraInfo] = useState(INITIAL_EXTRA_INFO);
  const [selectedMedia, setSelectedMedia] = useState<string[]>([]);
  const [selectedAmenities, setSelectedAmenities] = useState<string[]>([]);
  const [draggedPhotoId, setDraggedPhotoId] = useState<string | null>(null);
  const [photoDropTargetId, setPhotoDropTargetId] = useState<string | null>(null);
  const [finalGalleryIndex, setFinalGalleryIndex] = useState(0);
  const [locationCenter, setLocationCenter] = useState<LatLng>(INITIAL_LISTING_COORDS);
  const [locationMapCenter, setLocationMapCenter] = useState<LatLng>(INITIAL_LISTING_COORDS);
  const [locationMapZoom, setLocationMapZoom] = useState(16);
  const [locationStatus, setLocationStatus] = useState('Kliknij na mapie albo wpisz adres, a pinezka ustawi się automatycznie.');
  const skipLocationSearch = useRef(false);
  const photoInputRef = useRef<HTMLInputElement>(null);
  const descriptionRef = useRef<HTMLTextAreaElement>(null);
  const photoUrlsRef = useRef<string[]>([]);
  const isLocationStep = currentStep === 1;
  const isPhotosStep = currentStep === 2;
  const isExtraInfoStep = currentStep === 3;
  const isFinalPreviewStep = currentStep === 4;
  const showListingPreviewDetails = isPhotosStep || isExtraInfoStep;
  const mainPhoto = photos[0];
  const isRentTransaction = listingDetails.transaction.trim().toLowerCase() === 'wynajem';
  const numericPrice = Number(listingDetails.price.replace(',', '.'));
  const numericArea = Number(listingDetails.area.replace(',', '.'));
  const numericDeposit = Number(extraInfo.rent.replace(',', '.'));
  const numericSecurityDeposit = Number(extraInfo.deposit.replace(',', '.'));
  const pricePerMeter = numericPrice > 0 && numericArea > 0 ? Math.round(numericPrice / numericArea).toString() : '';
  const formattedPrice = numericPrice > 0 ? `${new Intl.NumberFormat('pl-PL').format(numericPrice)} zł` : 'Cena';
  const formattedMonthlyPrice = numericPrice > 0 ? `${new Intl.NumberFormat('pl-PL').format(numericPrice)} zł / miesiąc` : 'Cena / miesiąc';
  const formattedPricePerMeter = pricePerMeter ? `${new Intl.NumberFormat('pl-PL').format(Number(pricePerMeter))} zł/m²` : 'zł/m²';
  const formattedDeposit = numericDeposit > 0 ? `Czynsz administracyjny: ${new Intl.NumberFormat('pl-PL').format(numericDeposit)} zł` : 'Czynsz administracyjny: nie podano';
  const formattedSecurityDeposit = numericSecurityDeposit > 0 ? `${new Intl.NumberFormat('pl-PL').format(numericSecurityDeposit)} zł` : 'Nie podano';
  const previewTitle = listingDetails.title.trim() || 'Tytuł ogłoszenia';
  const normalizedVirtualTourUrl = extraInfo.virtualTourUrl.trim();
  const hasVirtualTour = normalizedVirtualTourUrl.length > 0;
  const virtualTourEmbedPreview = hasVirtualTour ? getVirtualTourEmbed(normalizedVirtualTourUrl) : null;

  const [publishState, setPublishState] = useState<'idle' | 'saving' | 'success' | 'error'>('idle');
  const [publishError, setPublishError] = useState('');
  const [virtualTourError, setVirtualTourError] = useState('');

  const handlePreviewVirtualTour = () => {
    try {
      const normalized = normalizeVirtualTourInput(extraInfo.virtualTourUrl);
      setVirtualTourError('');
      window.open(normalized, '_blank', 'noopener,noreferrer');
    } catch (error) {
      setVirtualTourError(error instanceof Error ? error.message : 'Nie udało się otworzyć linku spaceru.');
    }
  };

  const handlePublish = async () => {
    if (publishState === 'saving') {
      return;
    }
    const roomsValue = Number(listingDetails.rooms);
    const cityValue = location.city.trim();
    if (!listingDetails.title.trim() || !listingDetails.description.trim()) {
      setPublishState('error');
      setPublishError('Uzupełnij tytuł i opis ogłoszenia (krok „Szczegóły”).');
      return;
    }
    if (!cityValue) {
      setPublishState('error');
      setPublishError('Podaj miejscowość w kroku „Lokalizacja”.');
      return;
    }
    if (!(numericPrice > 0) || !(numericArea > 0) || !Number.isFinite(roomsValue) || roomsValue < 1) {
      setPublishState('error');
      setPublishError('Uzupełnij poprawną cenę, powierzchnię i liczbę pokoi.');
      return;
    }

    const addressValue = [location.street, location.building, location.district]
      .map((part) => part.trim())
      .filter(Boolean)
      .join(' ');

    setPublishState('saving');
    setPublishError('');
    try {
      const clean = (value: string) => {
        const trimmed = (value ?? '').trim();
        return trimmed && trimmed !== 'Wybierz' ? trimmed : undefined;
      };
      const yearNum = Number.parseInt(listingDetails.yearBuilt, 10);
      const rentNum = Number.parseFloat((extraInfo.rent ?? '').replace(',', '.'));
      const photoData = (await Promise.all(photos.slice(0, 8).map((photo) => imageUrlToDataUrl(photo.url))))
        .filter((value): value is string => Boolean(value));
      const virtualTourUrl = clean(extraInfo.virtualTourUrl);
      const normalizedVirtualTour = virtualTourUrl ? normalizeVirtualTourInput(virtualTourUrl) : undefined;

      const payload = {
        title: listingDetails.title.trim(),
        description: listingDetails.description.trim(),
        offerType: isRentTransaction ? 'RENT' : 'SALE',
        propertyType: listingDetails.propertyType === 'Dom' ? 'HOUSE' : 'APARTMENT',
        city: cityValue,
        address: addressValue || null,
        price: numericPrice,
        area: numericArea,
        rooms: Math.trunc(roomsValue),
        market: clean(listingDetails.market),
        district: clean(location.district),
        street: clean(location.street),
        building: clean(location.building),
        floor: clean(listingDetails.floor),
        buildingFloors: clean(isHouseProperty ? listingDetails.houseFloors : listingDetails.buildingFloors),
        yearBuilt: Number.isFinite(yearNum) ? yearNum : undefined,
        condition: clean(listingDetails.condition),
        ownership: clean(listingDetails.ownership),
        contactName: clean(listingDetails.contactName),
        contactPhone: clean(listingDetails.contactPhone),
        contactEmail: clean(listingDetails.contactEmail),
        rentExtra: Number.isFinite(rentNum) && rentNum > 0 ? rentNum : undefined,
        availableFrom: isRentTransaction ? clean(extraInfo.availableFrom) : undefined,
        furnishing: clean(extraInfo.furnishing),
        buildingType: clean(isHouseProperty ? listingDetails.houseType : extraInfo.buildingType),
        buildingMaterial: clean(extraInfo.buildingMaterial),
        windows: clean(extraInfo.windows),
        exposure: clean(extraInfo.exposure),
        roomHeight: clean(extraInfo.roomHeight),
        lat: locationCenter.lat,
        lng: locationCenter.lng,
        media: selectedMedia,
        amenities: selectedAmenities,
        photos: photoData,
        virtualTourUrl: normalizedVirtualTour,
      };

      await apiFetch('/listings', { method: 'POST', body: JSON.stringify(payload) });
      setPublishState('success');
    } catch (err) {
      setPublishState('error');
      setPublishError(err instanceof Error ? err.message : 'Nie udało się opublikować ogłoszenia.');
    }
  };

  const isHouseProperty = listingDetails.propertyType === 'Dom';
  const isSaleHouse = isHouseProperty && !isRentTransaction;
  const isLandProperty = listingDetails.propertyType === 'Działka';
  const showFlatFields = !isHouseProperty && !isLandProperty;
  const showRoomFields = !isLandProperty;
  const showBuildingFields = !isLandProperty;
  const showBuildingExtraFields = !isLandProperty;
  const visibleMediaOptions = mediaOptions.filter((option) => {
    if (isSaleHouse && option.label === 'Kanalizacja') {
      return false;
    }
    return !isLandProperty || ['Prąd', 'Gaz', 'Woda miejska', 'Kanalizacja'].includes(option.label);
  });
  const hiddenAmenityLabels = isLandProperty
    ? ['Balkon', 'Taras', 'Winda', 'Piwnica', 'Komórka lokatorska', 'Dostęp dla niepełnosprawnych', 'Monitoring']
    : isHouseProperty
      ? ['Winda', 'Komórka lokatorska', 'Garaż', 'Piwnica', 'Strych']
      : ['Strych', 'Ogrodzenie'];
  const availableAmenityOptions = isHouseProperty
    ? [...amenityOptions, ...houseAmenityOptions]
    : amenityOptions;
  const visibleAmenityOptions = availableAmenityOptions.filter((option) => {
    if (!isRentTransaction && (option.label === 'Dla studentów' || option.label === 'Zwierzęta mile widziane')) {
      return false;
    }
    return !hiddenAmenityLabels.includes(option.label);
  });
  const mediaBillingEntries = isRentTransaction
    ? [
      ...selectedMedia
        .map((label) => ({ key: label, label, options: getRentBillingOptions(label) }))
        .filter((item): item is { key: string; label: string; options: string[] } => Array.isArray(item.options)),
    ]
    : [];
  const amenityBillingEntries = isRentTransaction
    ? [
      ...(selectedAmenities.includes('Parking')
        ? [{ key: 'Parking', label: 'Parking', options: RENT_BILLING_OPTIONS_PARKING }]
        : []),
      ...(selectedAmenities.includes('Garaż')
        ? [{ key: 'Garaż', label: 'Garaż', options: RENT_BILLING_OPTIONS_PARKING }]
        : []),
    ]
    : [];
  const previewArea = listingDetails.area.trim() ? `${listingDetails.area.replace('.', ',')} m²` : 'Powierzchnia';
  const previewRooms = listingDetails.rooms !== 'Wybierz' ? `${listingDetails.rooms} ${listingDetails.rooms === '1' ? 'pokój' : ['2', '3', '4'].includes(listingDetails.rooms) ? 'pokoje' : 'pokoi'}` : 'Pokoje';
  const previewFloor = listingDetails.floor !== 'Wybierz' ? `Piętro ${listingDetails.floor}${listingDetails.buildingFloors !== 'Wybierz' ? `/${listingDetails.buildingFloors}` : ''}` : 'Piętro';
  const previewLocation = [
    location.city,
    location.district,
    location.estate,
    [location.street, location.building].filter(Boolean).join(' '),
  ].filter((part) => part.trim()).join(', ') || 'Lokalizacja';
  const previewMetaItems = [
    { icon: 'building', label: previewArea },
    ...(showRoomFields ? [{ icon: 'house', label: previewRooms }] : []),
    ...(showFlatFields ? [{ icon: 'city', label: previewFloor }] : []),
    { icon: 'check', label: listingDetails.market },
    ...(isExtraInfoStep && extraInfo.furnishing !== 'Wybierz' ? [{ icon: 'sofa', label: extraInfo.furnishing === 'Tak' ? 'Umeblowane' : `Umeblowanie: ${extraInfo.furnishing}` }] : []),
    ...(isExtraInfoStep && extraInfo.buildingType !== 'Wybierz' ? [{ icon: 'building', label: extraInfo.buildingType }] : []),
    ...(['Balkon', 'Taras', 'Ogródek', 'Winda', 'Parking', 'Garaż', 'Piwnica']
      .filter((label) => isExtraInfoStep && selectedAmenities.includes(label))
      .map((label) => ({ icon: label === 'Parking' || label === 'Garaż' ? 'car' : label === 'Ogródek' ? 'wifi' : 'building', label }))),
    ...(['Internet', 'Klimatyzacja', 'Ogrzewanie miejskie', 'Gaz']
      .filter((label) => isExtraInfoStep && selectedMedia.includes(label))
      .map((label) => ({ icon: label === 'Internet' ? 'wifi' : label === 'Klimatyzacja' ? 'snow' : label === 'Gaz' || label === 'Ogrzewanie miejskie' ? 'flame' : 'spark', label }))),
  ].slice(0, isExtraInfoStep ? 8 : 4);
  const finalGalleryImages = photos.length > 0
    ? photos.map((photo) => photo.url)
    : [heroImage, heroImage, cityImage, heroImage, cityImage];
  const activeFinalImage = finalGalleryImages[finalGalleryIndex] ?? finalGalleryImages[0];
  const finalFeatureItems = [
    ...selectedAmenities.filter((label) => visibleAmenityOptions.some((option) => option.label === label)).map((label) => ({ icon: label === 'Parking' || label === 'Garaż' ? 'car' : label === 'Ogródek' ? 'wifi' : 'building', label })),
    ...selectedMedia.filter((label) => visibleMediaOptions.some((option) => option.label === label)).map((label) => ({ icon: label === 'Internet' ? 'wifi' : label === 'Klimatyzacja' ? 'snow' : label === 'Gaz' || label.includes('Ogrzewanie') ? 'flame' : label === 'Telefon' ? 'phone' : 'spark', label })),
  ];
  const finalDetails = [
    { label: 'Rynek', value: listingDetails.market },
    { label: 'Forma własności', value: listingDetails.ownership },
    ...(showBuildingFields ? [{ label: 'Rok budowy', value: listingDetails.yearBuilt || 'Nie podano' }] : []),
    ...(isHouseProperty ? [{ label: 'Typ domu', value: listingDetails.houseType !== 'Wybierz' ? listingDetails.houseType : 'Nie podano' }] : []),
    ...(isHouseProperty ? [{ label: 'Liczba kondygnacji', value: listingDetails.houseFloors !== 'Wybierz' ? listingDetails.houseFloors : 'Nie podano' }] : []),
    ...(isSaleHouse ? [{ label: 'Powierzchnia działki', value: extraInfo.plotArea ? `${extraInfo.plotArea} m²` : 'Nie podano' }] : []),
    ...(isHouseProperty ? [{ label: 'Garaż', value: listingDetails.houseGarage !== 'Wybierz' ? listingDetails.houseGarage : 'Nie podano' }] : []),
    ...(isHouseProperty ? [{ label: 'Podpiwniczenie', value: listingDetails.houseBasement !== 'Wybierz' ? listingDetails.houseBasement : 'Nie podano' }] : []),
    ...(isHouseProperty ? [{ label: 'Strych', value: listingDetails.houseAttic !== 'Wybierz' ? listingDetails.houseAttic : 'Nie podano' }] : []),
    ...(isSaleHouse ? [{ label: 'Dach', value: extraInfo.roofType !== 'Wybierz' ? extraInfo.roofType : 'Nie podano' }] : []),
    ...(isSaleHouse ? [{ label: 'Pokrycie dachu', value: extraInfo.roofCover !== 'Wybierz' ? extraInfo.roofCover : 'Nie podano' }] : []),
    ...(isSaleHouse && selectedAmenities.includes('Ogrodzenie') ? [{ label: 'Ogrodzenie', value: extraInfo.fencing !== 'Wybierz' ? extraInfo.fencing : 'Nie podano' }] : []),
    ...(isSaleHouse ? [{ label: 'Dojazd', value: extraInfo.accessRoad !== 'Wybierz' ? extraInfo.accessRoad : 'Nie podano' }] : []),
    ...(isSaleHouse && selectedMedia.includes('Gospodarka ściekowa') ? [{ label: 'Gospodarka ściekowa', value: extraInfo.sewageType !== 'Wybierz' ? extraInfo.sewageType : 'Nie podano' }] : []),
    ...(isRentTransaction ? [{ label: 'Czynsz administracyjny', value: extraInfo.rent ? `${extraInfo.rent} zł` : 'Nie podano' }] : []),
    ...(isRentTransaction ? [{ label: 'Kaucja', value: formattedSecurityDeposit }] : []),
    ...(isRentTransaction ? [{ label: 'Okres najmu', value: extraInfo.rentalPeriod !== 'Wybierz' ? extraInfo.rentalPeriod : 'Nie podano' }] : []),
    ...(isRentTransaction ? [...mediaBillingEntries, ...amenityBillingEntries].map((item) => {
      const mode = extraInfo.mediaBilling[item.key] ?? item.options[0];
      const amount = (extraInfo.mediaBillingAmounts[item.key] ?? '').trim();
      const value = billingRequiresAmount(mode)
        ? (amount ? `Kwota: ${amount} zł` : 'Kwota: nie podano')
        : mode;
      return {
      label: `${item.label} (rozliczenie)`,
      value,
      };
    }) : []),
    ...(showBuildingFields ? [{ label: 'Stan nieruchomości', value: listingDetails.condition }] : []),
    ...(showBuildingExtraFields ? [{ label: 'Umeblowanie', value: extraInfo.furnishing }] : []),
    ...(!isSaleHouse ? [{ label: 'Dostępne od', value: extraInfo.availableFrom || 'Do uzgodnienia' }] : []),
    { label: 'Ogłoszenie dodane', value: 'Dzisiaj' },
  ];

  useEffect(() => {
    try {
      const raw = window.localStorage.getItem(EDIT_LISTING_DRAFT_STORAGE_KEY);
      if (!raw) {
        return;
      }

      const parsed = JSON.parse(raw) as {
        listingDetails?: Partial<typeof INITIAL_LISTING_DETAILS>;
        location?: Partial<ListingLocation>;
        extraInfo?: Partial<typeof INITIAL_EXTRA_INFO>;
        selectedMedia?: string[];
        selectedAmenities?: string[];
      };

      if (parsed.listingDetails) {
        setListingDetails((current) => ({ ...current, ...parsed.listingDetails }));
      }
      if (parsed.location) {
        setLocation((current) => ({ ...current, ...parsed.location }));
      }
      if (parsed.extraInfo) {
        setExtraInfo((current) => ({ ...current, ...parsed.extraInfo }));
      }
      if (Array.isArray(parsed.selectedMedia)) {
        setSelectedMedia(parsed.selectedMedia);
      }
      if (Array.isArray(parsed.selectedAmenities)) {
        setSelectedAmenities(parsed.selectedAmenities);
      }

      setCurrentStep(0);
      setLocationStatus('Dane ogłoszenia zostały wczytane do edycji.');
      window.localStorage.removeItem(EDIT_LISTING_DRAFT_STORAGE_KEY);
    } catch {
      window.localStorage.removeItem(EDIT_LISTING_DRAFT_STORAGE_KEY);
    }
  }, []);

  const goToStep = (step: number) => {
    setCurrentStep(step);
    window.requestAnimationFrame(() => {
      document.querySelector('.add-page')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
    });
  };
  const showPreviousFinalPhoto = () => {
    setFinalGalleryIndex((current) => (current - 1 + finalGalleryImages.length) % finalGalleryImages.length);
  };
  const showNextFinalPhoto = () => {
    setFinalGalleryIndex((current) => (current + 1) % finalGalleryImages.length);
  };
  const updateLocation = (field: keyof ListingLocation, value: string) => {
    setLocation((current) => ({ ...current, [field]: value }));
  };

  const updateListingDetails = (field: keyof typeof INITIAL_LISTING_DETAILS, value: string) => {
    setListingDetails((current) => ({ ...current, [field]: value }));
  };

  const formatDescription = (format: 'bold' | 'list') => {
    const textarea = descriptionRef.current;
    if (!textarea) {
      return;
    }
    const start = textarea.selectionStart;
    const end = textarea.selectionEnd;
    const selectedText = listingDetails.description.slice(start, end);
    const hasSelection = start !== end;
    const formatStart = format === 'bold' && !hasSelection && listingDetails.description ? 0 : start;
    const formatEnd = format === 'bold' && !hasSelection && listingDetails.description ? listingDetails.description.length : end;
    const text = listingDetails.description.slice(formatStart, formatEnd) || (format === 'list' ? 'punkt listy' : 'tekst');
    const formattedText = format === 'bold' ? `**${text}**` : `${formatStart > 0 && listingDetails.description[formatStart - 1] !== '\n' ? '\n' : ''}- ${text}`;
    const nextDescription = `${listingDetails.description.slice(0, formatStart)}${formattedText}${listingDetails.description.slice(formatEnd)}`.slice(0, 3000);
    updateListingDetails('description', nextDescription);
    window.requestAnimationFrame(() => {
      textarea.focus();
      textarea.setSelectionRange(formatStart + formattedText.length, formatStart + formattedText.length);
    });
  };

  const updateExtraInfo = (field: keyof typeof INITIAL_EXTRA_INFO, value: string) => {
    if (field === 'virtualTourUrl' && virtualTourError) {
      setVirtualTourError('');
    }
    setExtraInfo((current) => ({ ...current, [field]: value }));
  };

  const updateMediaBilling = (key: string, value: string) => {
    setExtraInfo((current) => ({
      ...current,
      mediaBilling: {
        ...current.mediaBilling,
        [key]: value,
      },
      mediaBillingAmounts: billingRequiresAmount(value)
        ? current.mediaBillingAmounts
        : Object.fromEntries(Object.entries(current.mediaBillingAmounts).filter(([entryKey]) => entryKey !== key)),
    }));
  };

  const updateMediaBillingAmount = (key: string, value: string) => {
    setExtraInfo((current) => ({
      ...current,
      mediaBillingAmounts: {
        ...current.mediaBillingAmounts,
        [key]: value,
      },
    }));
  };

  const toggleOption = (value: string, setter: Dispatch<SetStateAction<string[]>>) => {
    setter((current) => current.includes(value) ? current.filter((item) => item !== value) : [...current, value]);
  };

  const addPhotos = (files: FileList | File[]) => {
    const imageFiles = Array.from(files).filter((file) => ['image/jpeg', 'image/png'].includes(file.type) && file.size <= 10 * 1024 * 1024);
    if (!imageFiles.length) {
      return;
    }
    setPhotos((current) => {
      const freeSlots = Math.max(20 - current.length, 0);
      const nextPhotos = imageFiles.slice(0, freeSlots).map((file) => ({
        id: `${file.name}-${file.lastModified}-${safeRandomId()}`,
        name: file.name,
        url: URL.createObjectURL(file),
      }));
      return [...current, ...nextPhotos];
    });
  };

  const handlePhotoInput = (event: ReactChangeEvent<HTMLInputElement>) => {
    if (event.target.files) {
      addPhotos(event.target.files);
    }
    event.target.value = '';
  };

  const handlePhotoDrop = (event: ReactDragEvent<HTMLLabelElement>) => {
    event.preventDefault();
    addPhotos(event.dataTransfer.files);
  };

  const removePhoto = (photoId: string) => {
    setPhotos((current) => {
      const removedPhoto = current.find((photo) => photo.id === photoId);
      if (removedPhoto) {
        URL.revokeObjectURL(removedPhoto.url);
      }
      return current.filter((photo) => photo.id !== photoId);
    });
  };

  const movePhoto = (photoId: string, direction: -1 | 1) => {
    setPhotos((current) => {
      const index = current.findIndex((photo) => photo.id === photoId);
      const nextIndex = index + direction;
      if (index < 0 || nextIndex < 0 || nextIndex >= current.length) {
        return current;
      }
      const nextPhotos = [...current];
      [nextPhotos[index], nextPhotos[nextIndex]] = [nextPhotos[nextIndex], nextPhotos[index]];
      return nextPhotos;
    });
  };

  const reorderPhoto = (fromPhotoId: string, toPhotoId: string) => {
    if (fromPhotoId === toPhotoId) {
      return;
    }
    setPhotos((current) => {
      const fromIndex = current.findIndex((photo) => photo.id === fromPhotoId);
      const toIndex = current.findIndex((photo) => photo.id === toPhotoId);
      if (fromIndex < 0 || toIndex < 0) {
        return current;
      }
      const nextPhotos = [...current];
      const [draggedPhoto] = nextPhotos.splice(fromIndex, 1);
      nextPhotos.splice(toIndex, 0, draggedPhoto);
      return nextPhotos;
    });
  };

  const handlePhotoDragStart = (event: ReactDragEvent<HTMLElement>, photoId: string) => {
    setDraggedPhotoId(photoId);
    event.dataTransfer.effectAllowed = 'move';
    event.dataTransfer.setData('text/plain', photoId);
  };

  const handlePhotoDropOnThumb = (event: ReactDragEvent<HTMLElement>, photoId: string) => {
    event.preventDefault();
    const sourcePhotoId = draggedPhotoId || event.dataTransfer.getData('text/plain');
    if (sourcePhotoId) {
      reorderPhoto(sourcePhotoId, photoId);
    }
    setDraggedPhotoId(null);
    setPhotoDropTargetId(null);
  };

  useEffect(() => {
    photoUrlsRef.current = photos.map((photo) => photo.url);
  }, [photos]);

  useEffect(() => {
    setFinalGalleryIndex((current) => Math.min(current, finalGalleryImages.length - 1));
  }, [finalGalleryImages.length]);

  useEffect(() => {
    setSelectedMedia((current) => current.filter((label) => visibleMediaOptions.some((option) => option.label === label)));
    setSelectedAmenities((current) => current.filter((label) => visibleAmenityOptions.some((option) => option.label === label)));
    setListingDetails((current) => ({
      ...current,
      rooms: showRoomFields ? current.rooms : 'Wybierz',
      floor: showFlatFields ? current.floor : 'Wybierz',
      buildingFloors: showFlatFields ? current.buildingFloors : 'Wybierz',
      yearBuilt: showBuildingFields ? current.yearBuilt : '',
      condition: showBuildingFields ? current.condition : 'Wybierz',
    }));
    if (!showBuildingExtraFields) {
      setExtraInfo((current) => ({
        ...current,
        furnishing: 'Wybierz',
        buildingType: 'Wybierz',
        buildingMaterial: 'Wybierz',
        windows: 'Wybierz',
        exposure: 'Wybierz',
        roomHeight: '',
      }));
    }
  }, [listingDetails.propertyType]);

  useEffect(() => {
    if (!isRentTransaction) {
      setExtraInfo((current) => ({ ...current, rent: '', deposit: '', rentalPeriod: 'Wybierz', mediaBilling: {}, mediaBillingAmounts: {} }));
    }
  }, [isRentTransaction]);

  useEffect(() => {
    if (!isRentTransaction) {
      return;
    }

    const allowedKeys = new Set([...mediaBillingEntries, ...amenityBillingEntries].map((item) => item.key));
    setExtraInfo((current) => {
      const nextBilling = Object.fromEntries(
        Object.entries(current.mediaBilling).filter(([key]) => allowedKeys.has(key)),
      );
      const nextAmounts = Object.fromEntries(
        Object.entries(current.mediaBillingAmounts)
          .filter(([key]) => allowedKeys.has(key) && billingRequiresAmount(nextBilling[key] ?? '')),
      );
      const unchanged = Object.keys(nextBilling).length === Object.keys(current.mediaBilling).length
        && Object.keys(nextBilling).every((key) => current.mediaBilling[key] === nextBilling[key])
        && Object.keys(nextAmounts).length === Object.keys(current.mediaBillingAmounts).length
        && Object.keys(nextAmounts).every((key) => current.mediaBillingAmounts[key] === nextAmounts[key]);
      if (unchanged) {
        return current;
      }
      return { ...current, mediaBilling: nextBilling, mediaBillingAmounts: nextAmounts };
    });
  }, [isRentTransaction, mediaBillingEntries, amenityBillingEntries]);

  useEffect(() => {
    return () => {
      photoUrlsRef.current.forEach((url) => URL.revokeObjectURL(url));
    };
  }, []);

  useEffect(() => {
    if (!isLocationStep) {
      return;
    }
    if (skipLocationSearch.current) {
      skipLocationSearch.current = false;
      return;
    }
    const controller = new AbortController();
    const timer = window.setTimeout(() => {
      setLocationStatus('Szukam adresu na mapie...');
      geocodeListingLocation(location, controller.signal)
        .then((point) => {
          if (!point) {
            setLocationStatus('Nie znaleziono dokładnego adresu. Przybliż mapę ręcznie.');
            return;
          }
          const nextCenter = clampCenter(point);
          setLocationCenter(nextCenter);
          setLocationMapCenter(nextCenter);
          setLocationMapZoom(locationZoom(location));
          setLocationStatus('Pinezka została ustawiona na podstawie wpisanego adresu.');
        })
        .catch((error) => {
          if (error instanceof DOMException && error.name === 'AbortError') {
            return;
          }
          setLocationStatus('Nie udało się pobrać lokalizacji. Spróbuj kliknąć punkt na mapie.');
        });
    }, 650);
    return () => {
      window.clearTimeout(timer);
      controller.abort();
    };
  }, [isLocationStep, location]);

  const pickLocationPoint = (point: LatLng) => {
    const nextCenter = clampCenter(point);
    setLocationCenter(nextCenter);
    setLocationMapCenter(nextCenter);
    setLocationMapZoom((current) => Math.max(current, 16));
    setLocationStatus('Pobieram adres z wybranego punktu...');
    const controller = new AbortController();
    reverseGeocodeListingLocation(nextCenter, controller.signal)
      .then((details) => {
        skipLocationSearch.current = true;
        setLocation((current) => ({
          ...current,
          city: details.city ?? '',
          district: details.district ?? '',
          estate: details.estate ?? '',
          street: details.street ?? '',
          building: details.building ?? '',
          unit: '',
        }));
        setLocationStatus('Pola lokalizacji zostały uzupełnione z mapy.');
      })
      .catch(() => setLocationStatus('Nie udało się odczytać adresu z mapy. Przesuń pinezkę bliżej budynku.'));
  };

  return (
    <section className="add-page" aria-label="Dodaj ogłoszenie">
      <div className="add-container">
        <div className="breadcrumb">
          <span>Strona główna</span>
          <Icon name="chevron" />
          <strong>Dodaj ogłoszenie</strong>
        </div>

        {!isPhotosStep && !isExtraInfoStep && !isFinalPreviewStep && (
          <div className="add-title">
            <h1>
              Dodaj ogłoszenie <span>za darmo</span>
            </h1>
            <p>Wypełnij formularz i dotrzyj do tysięcy zainteresowanych.</p>
          </div>
        )}

        <div className="add-progress" aria-label="Postęp formularza">
          {addProgressSteps.map((step, index) => (
            <div className={index === currentStep ? 'active' : index < currentStep ? 'completed' : ''} key={step}>
              <span>{index < currentStep ? <Icon name="check" /> : index + 1}</span>
              <strong>{step}</strong>
            </div>
          ))}
        </div>

        <div className={`add-layout ${isFinalPreviewStep ? 'final-preview-layout' : ''}`}>
          <div className="add-form-column">
            {!isLocationStep && !isPhotosStep && !isExtraInfoStep && !isFinalPreviewStep && (
              <>
                <section className="add-form-card">
                  <h2>Podstawowe informacje</h2>
                  <div className="add-three-grid">
                    <AddSelect label="Transakcja" value={listingDetails.transaction} options={listingDetailsSelectOptions.transaction} onChange={(value) => updateListingDetails('transaction', value)} />
                    <AddSelect label="Rodzaj nieruchomości" value={listingDetails.propertyType} options={listingDetailsSelectOptions.propertyType} onChange={(value) => updateListingDetails('propertyType', value)} />
                    <AddSelect label="Rynek" value={listingDetails.market} options={listingDetailsSelectOptions.market} onChange={(value) => updateListingDetails('market', value)} />
                  </div>

                  <label className="add-field full">
                    <span>Tytuł ogłoszenia</span>
                    <input value={listingDetails.title} maxLength={100} onChange={(event) => updateListingDetails('title', event.target.value)} placeholder="Np. 3-pokojowe mieszkanie po remoncie, gotowe do wprowadzenia" />
                    <small>{listingDetails.title.length}/100</small>
                  </label>

                  <div className="add-field full">
                    <span>Opis ogłoszenia</span>
                    <div className="editor-box">
                      <div className="editor-toolbar">
                        <button type="button" onClick={() => formatDescription('bold')} aria-label="Pogrub zaznaczony tekst">B</button>
                        <button type="button" onClick={() => formatDescription('list')} aria-label="Dodaj punkt listy">•</button>
                      </div>
                      <textarea ref={descriptionRef} value={listingDetails.description} maxLength={3000} onChange={(event) => updateListingDetails('description', event.target.value)} placeholder="Opisz nieruchomość, jej stan, okolicę, komunikację, dodatkowe atuty..." />
                      <small>{listingDetails.description.length}/3000</small>
                    </div>
                  </div>
                </section>

                <section className="add-form-card">
                  <h2>Szczegóły nieruchomości</h2>
                  <div className="add-three-grid">
                    <AddInput label={isRentTransaction ? 'Czynsz miesięczny' : 'Cena'} value={listingDetails.price} suffix="zł" placeholder={isRentTransaction ? 'Np. 3200' : 'Np. 720000'} onChange={(value) => updateListingDetails('price', value)} />
                    {isRentTransaction ? (
                      <AddInput label="Czynsz administracyjny" value={extraInfo.rent} suffix="zł" placeholder="Np. 3000" onChange={(value) => updateExtraInfo('rent', value)} />
                    ) : (
                      <AddInput label="Cena za m²" value={pricePerMeter} suffix="zł/m²" readOnly />
                    )}
                    <AddInput label="Powierzchnia" value={listingDetails.area} suffix="m²" placeholder="Np. 55" onChange={(value) => updateListingDetails('area', value)} />
                    {showRoomFields && <AddSelect label="Liczba pokoi" value={listingDetails.rooms} options={listingDetailsSelectOptions.rooms} onChange={(value) => updateListingDetails('rooms', value)} />}
                    {showFlatFields && <AddSelect label="Piętro" value={listingDetails.floor} options={listingDetailsSelectOptions.floor} onChange={(value) => updateListingDetails('floor', value)} />}
                    {showFlatFields && <AddSelect label="Liczba pięter w budynku" value={listingDetails.buildingFloors} options={listingDetailsSelectOptions.buildingFloors} onChange={(value) => updateListingDetails('buildingFloors', value)} />}
                    {isHouseProperty && <AddSelect label="Typ domu" value={listingDetails.houseType} options={listingDetailsSelectOptions.houseType} onChange={(value) => updateListingDetails('houseType', value)} />}
                    {isHouseProperty && <AddSelect label="Liczba kondygnacji" value={listingDetails.houseFloors} options={listingDetailsSelectOptions.houseFloors} onChange={(value) => updateListingDetails('houseFloors', value)} />}
                    {isHouseProperty && <AddSelect label="Garaż" value={listingDetails.houseGarage} options={listingDetailsSelectOptions.houseGarage} onChange={(value) => updateListingDetails('houseGarage', value)} />}
                    {isHouseProperty && <AddSelect label="Podpiwniczenie" value={listingDetails.houseBasement} options={listingDetailsSelectOptions.houseBasement} onChange={(value) => updateListingDetails('houseBasement', value)} />}
                    {isHouseProperty && <AddSelect label="Strych" value={listingDetails.houseAttic} options={listingDetailsSelectOptions.houseAttic} onChange={(value) => updateListingDetails('houseAttic', value)} />}
                    {showBuildingFields && <AddInput label="Rok budowy" value={listingDetails.yearBuilt} placeholder="Np. 2015" onChange={(value) => updateListingDetails('yearBuilt', value)} />}
                    {showBuildingFields && <AddSelect label="Stan nieruchomości" value={listingDetails.condition} options={listingDetailsSelectOptions.condition} onChange={(value) => updateListingDetails('condition', value)} />}
                    <AddSelect label="Forma własności" value={listingDetails.ownership} options={listingDetailsSelectOptions.ownership} onChange={(value) => updateListingDetails('ownership', value)} />
                  </div>
                </section>

                <section className="add-form-card">
                  <h2>Dane kontaktowe</h2>
                  <div className="add-three-grid">
                    <AddInput label="Imię" value={listingDetails.contactName} placeholder="Np. Jan" onChange={(value) => updateListingDetails('contactName', value)} />
                    <AddInput label="Telefon" value={listingDetails.contactPhone} placeholder="Np. 600 123 456" onChange={(value) => updateListingDetails('contactPhone', value)} />
                    <AddInput label="E-mail" value={listingDetails.contactEmail} type="email" placeholder="Np. jan.kowalski@email.pl" onChange={(value) => updateListingDetails('contactEmail', value)} />
                  </div>
                  <div className="contact-actions">
                    <button className="next-step-button" type="button" onClick={() => goToStep(1)}>
                      Dalej: Lokalizacja
                      <Icon name="arrow" />
                    </button>
                  </div>
                </section>

                <p className="secure-note">
                  <Icon name="shield" />
                  Twoje dane są bezpieczne. Nie udostępniamy ich osobom trzecim.
                </p>
              </>
            )}

            {isLocationStep && (
              <section className="add-form-card add-location-card">
                <h2>Lokalizacja nieruchomości</h2>
                <p className="add-location-subtitle">Podaj dokładną lokalizację nieruchomości.</p>

                <div className="add-location-grid">
                  <label className="add-field">
                    <span>Miasto <span className="required-star">*</span></span>
                    <input value={location.city} onChange={(event) => updateLocation('city', event.target.value)} placeholder="Wpisz miasto" />
                  </label>

                  <label className="add-field">
                    <span>Dzielnica <span className="optional-label">(opcjonalnie)</span></span>
                    <input value={location.district} onChange={(event) => updateLocation('district', event.target.value)} placeholder="Wpisz dzielnicę" />
                  </label>

                  <label className="add-field">
                    <span>Osiedle <span className="optional-label">(opcjonalnie)</span></span>
                    <input value={location.estate} onChange={(event) => updateLocation('estate', event.target.value)} placeholder="Wpisz osiedle" />
                  </label>

                  <label className="add-field">
                    <span>Ulica <span className="required-star">*</span></span>
                    <input value={location.street} onChange={(event) => updateLocation('street', event.target.value)} placeholder="Wpisz ulicę" />
                  </label>

                  <label className="add-field">
                    <span>Numer budynku <span className="required-star">*</span></span>
                    <input value={location.building} onChange={(event) => updateLocation('building', event.target.value)} placeholder="Nr" />
                  </label>

                  <label className="add-field">
                    <span>Numer lokalu <span className="optional-label">(opcjonalnie)</span></span>
                    <input value={location.unit} onChange={(event) => updateLocation('unit', event.target.value)} placeholder="Nr" />
                  </label>
                </div>

                <LocationPickerMap
                  center={locationMapCenter}
                  marker={locationCenter}
                  zoom={locationMapZoom}
                  label={[location.street, location.building].filter(Boolean).join(' ') || location.city}
                  onChange={(center, zoom) => { setLocationMapCenter(center); setLocationMapZoom(zoom); }}
                  onPick={pickLocationPoint}
                />

                <p className="add-location-note">
                  <Icon name="pin" />
                  {locationStatus}
                </p>

                <div className="add-location-actions">
                  <button className="back-step-button" type="button" onClick={() => goToStep(0)}>
                    Wstecz
                  </button>
                  <button className="next-step-button" type="button" onClick={() => goToStep(2)}>
                    Dalej: Zdjęcia
                    <Icon name="arrow" />
                  </button>
                </div>
              </section>
            )}

            {isPhotosStep && (
              <>
                <div className="add-title add-step-title">
                  <h1>Dodaj zdjęcia</h1>
                  <p>Dodaj zdjęcia, które najlepiej prezentują Twoją nieruchomość.</p>
                </div>

                <section className="add-form-card add-photos-card">
                  <h2>Zdjęcia nieruchomości</h2>

                  <label className="photo-upload-box" onDragOver={(event) => event.preventDefault()} onDrop={handlePhotoDrop}>
                    <input ref={photoInputRef} type="file" accept="image/png,image/jpeg" multiple onChange={handlePhotoInput} />
                    <Icon name="upload" />
                    <strong>Kliknij lub przeciągnij zdjęcia tutaj</strong>
                    <span>Dodaj do 20 zdjęć w formacie JPG, PNG (max. 10 MB)</span>
                  </label>

                  <p className="photo-tip-note">
                    <Icon name="camera" />
                    <span>
                      <strong>Dobre zdjęcia przyciągają więcej zainteresowanych</strong>
                      <small>Pokaż każde pomieszczenie, zadbaj o dobre oświetlenie i porządek.</small>
                    </span>
                  </p>

                  <div className="added-photos-head">
                    <h3>Dodane zdjęcia ({photos.length}/20)</h3>
                  </div>

                  {photos.length > 0 ? (
                    <div className="added-photos-grid">
                      {photos.map((photo, index) => (
                        <article
                          className={`added-photo ${draggedPhotoId === photo.id ? 'dragging' : ''} ${photoDropTargetId === photo.id && draggedPhotoId !== photo.id ? 'drop-target' : ''}`}
                          draggable
                          key={photo.id}
                          onDragStart={(event) => handlePhotoDragStart(event, photo.id)}
                          onDragEnter={() => setPhotoDropTargetId(photo.id)}
                          onDragOver={(event) => event.preventDefault()}
                          onDragEnd={() => { setDraggedPhotoId(null); setPhotoDropTargetId(null); }}
                          onDrop={(event) => handlePhotoDropOnThumb(event, photo.id)}
                        >
                          <img src={photo.url} alt={photo.name} />
                          {index === 0 && <span className="main-photo-badge">Główne</span>}
                          <button className="remove-photo-button" type="button" aria-label={`Usuń zdjęcie ${photo.name}`} onClick={() => removePhoto(photo.id)}>×</button>
                          <div className="photo-order-controls" aria-label="Zmień kolejność zdjęcia">
                            <button type="button" disabled={index === 0} onClick={() => movePhoto(photo.id, -1)} aria-label={`Przesuń zdjęcie ${photo.name} w lewo`}>‹</button>
                            <button type="button" disabled={index === photos.length - 1} onClick={() => movePhoto(photo.id, 1)} aria-label={`Przesuń zdjęcie ${photo.name} w prawo`}>›</button>
                          </div>
                        </article>
                      ))}
                    </div>
                  ) : (
                    <div className="empty-photos-box">
                      <Icon name="camera" />
                      <p>Nie dodano jeszcze zdjęć. Pierwsze dodane zdjęcie będzie zdjęciem głównym.</p>
                    </div>
                  )}

                  <div className="photo-tour-card" aria-label="Wirtualny spacer 3D">
                    <div className="photo-tour-head">
                      <span className="photo-tour-badge">360°</span>
                      <div>
                        <strong>Wirtualny spacer 3D (opcjonalnie)</strong>
                        <small>Wklej link do spaceru z YouTube 360, Matterport lub Kuula.</small>
                      </div>
                    </div>
                    <label className="add-field full photo-tour-input">
                      <span>Link do spaceru</span>
                      <input
                        type="url"
                        value={extraInfo.virtualTourUrl}
                        onChange={(event) => updateExtraInfo('virtualTourUrl', event.target.value)}
                        placeholder="https://my.matterport.com/show/?m=..."
                      />
                      <small>Po publikacji użytkownik zobaczy interaktywny spacer na karcie ogłoszenia.</small>
                    </label>
                    <div className="photo-tour-actions">
                      <button type="button" className="admin-btn" onClick={handlePreviewVirtualTour}>
                        Testuj spacer
                      </button>
                      <button
                        type="button"
                        className="admin-btn"
                        onClick={() => {
                          setExtraInfo((current) => ({ ...current, virtualTourUrl: '' }));
                          setVirtualTourError('');
                        }}
                        disabled={!extraInfo.virtualTourUrl.trim()}
                      >
                        Wyczyść
                      </button>
                    </div>
                    {virtualTourEmbedPreview && (
                      <p className="photo-tour-provider">Wykryto dostawcę: {virtualTourEmbedPreview.providerLabel}</p>
                    )}
                    {virtualTourError && <p className="photo-tour-error" role="alert">{virtualTourError}</p>}
                  </div>

                  <div className="add-location-actions photos-actions">
                    <button className="back-step-button" type="button" onClick={() => goToStep(1)}>
                      Wstecz
                    </button>
                    <button className="next-step-button" type="button" onClick={() => goToStep(3)}>
                      Dalej: Dodatkowe informacje
                      <Icon name="arrow" />
                    </button>
                  </div>
                </section>
              </>
            )}

            {isExtraInfoStep && (
              <>
                <div className="add-title add-step-title">
                  <h1>Dodatkowe informacje</h1>
                  <p>Podaj więcej szczegółów, które pomogą wyróżnić Twoje ogłoszenie.</p>
                </div>

                <section className="add-form-card add-extra-card">
                  <div className="extra-section">
                    <h2>Informacje uzupełniające</h2>
                    <div className="extra-details-grid">
                      {isRentTransaction && <AddSelect label="Okres najmu" value={extraInfo.rentalPeriod} options={extraInfoSelectOptions.rentalPeriod} onChange={(value) => updateExtraInfo('rentalPeriod', value)} />}
                      {isRentTransaction && <AddInput label="Kaucja" value={extraInfo.deposit} suffix="zł" placeholder="Np. 6000" onChange={(value) => updateExtraInfo('deposit', value)} />}
                      {!isSaleHouse && <AddInput label="Dostępne od" value={extraInfo.availableFrom} type="date" onChange={(value) => updateExtraInfo('availableFrom', value)} />}
                      {isSaleHouse && <AddInput label="Powierzchnia działki" value={extraInfo.plotArea} suffix="m²" placeholder="Np. 650" onChange={(value) => updateExtraInfo('plotArea', value)} />}
                      {showBuildingExtraFields && <AddSelect label="Umeblowanie" value={extraInfo.furnishing} options={extraInfoSelectOptions.furnishing} onChange={(value) => updateExtraInfo('furnishing', value)} />}
                      {showBuildingExtraFields && !isHouseProperty && <AddSelect label="Typ zabudowy" value={extraInfo.buildingType} options={extraInfoSelectOptions.buildingType} onChange={(value) => updateExtraInfo('buildingType', value)} />}
                      {isSaleHouse && <AddSelect label="Dach" value={extraInfo.roofType} options={extraInfoSelectOptions.roofType} onChange={(value) => updateExtraInfo('roofType', value)} />}
                      {isSaleHouse && <AddSelect label="Pokrycie dachu" value={extraInfo.roofCover} options={extraInfoSelectOptions.roofCover} onChange={(value) => updateExtraInfo('roofCover', value)} />}
                      {showBuildingExtraFields && <AddSelect label="Materiał budynku" value={extraInfo.buildingMaterial} options={extraInfoSelectOptions.buildingMaterial} onChange={(value) => updateExtraInfo('buildingMaterial', value)} />}
                      {showBuildingExtraFields && <AddSelect label="Okna" value={extraInfo.windows} options={extraInfoSelectOptions.windows} onChange={(value) => updateExtraInfo('windows', value)} />}
                      {showBuildingExtraFields && <AddSelect label="Ekspozycja" value={extraInfo.exposure} options={extraInfoSelectOptions.exposure} onChange={(value) => updateExtraInfo('exposure', value)} />}
                      {isSaleHouse && <AddSelect label="Dojazd" value={extraInfo.accessRoad} options={extraInfoSelectOptions.accessRoad} onChange={(value) => updateExtraInfo('accessRoad', value)} />}
                      {showBuildingExtraFields && <AddInput label="Wysokość pomieszczeń" value={extraInfo.roomHeight} suffix="cm" onChange={(value) => updateExtraInfo('roomHeight', value)} />}
                    </div>
                  </div>

                  <div className="extra-section">
                    <h2>{isSaleHouse ? 'Media' : 'Media i opłaty'}</h2>
                    <div className="extra-option-grid media-option-grid">
                      {visibleMediaOptions.map((option) => (
                        <label className={selectedMedia.includes(option.label) ? 'selected' : ''} key={option.label}>
                          <input type="checkbox" checked={selectedMedia.includes(option.label)} onChange={() => toggleOption(option.label, setSelectedMedia)} />
                          <Icon name={option.icon} />
                          <span>{option.label}</span>
                        </label>
                      ))}
                    </div>

                    {isSaleHouse && selectedMedia.includes('Gospodarka ściekowa') && (
                      <div className="extra-details-grid" style={{ marginTop: 12 }}>
                        <AddSelect label="Gospodarka ściekowa" value={extraInfo.sewageType} options={extraInfoSelectOptions.sewageType} onChange={(value) => updateExtraInfo('sewageType', value)} />
                      </div>
                    )}

                    {isRentTransaction && mediaBillingEntries.length > 0 && (
                      <div className="media-billing-panel">
                        <h3>Sposób rozliczenia wybranych opłat</h3>
                        <div className="media-billing-grid">
                          {mediaBillingEntries.map((item) => (
                            <label className="media-billing-row" key={item.key}>
                              <span>{item.label}</span>
                              <div className="media-billing-controls">
                                <select
                                  value={extraInfo.mediaBilling[item.key] ?? item.options[0]}
                                  onChange={(event) => updateMediaBilling(item.key, event.target.value)}
                                >
                                  {item.options.map((option) => (
                                    <option key={`${item.key}-${option}`} value={option}>{option}</option>
                                  ))}
                                </select>
                                {billingRequiresAmount(extraInfo.mediaBilling[item.key] ?? item.options[0]) && (
                                  <input
                                    type="text"
                                    inputMode="decimal"
                                    placeholder={(extraInfo.mediaBilling[item.key] ?? item.options[0]).includes('/mies.') ? 'Kwota / mies. (zł)' : 'Kwota (zł)'}
                                    value={extraInfo.mediaBillingAmounts[item.key] ?? ''}
                                    onChange={(event) => updateMediaBillingAmount(item.key, event.target.value)}
                                  />
                                )}
                              </div>
                            </label>
                          ))}
                        </div>
                      </div>
                    )}
                  </div>

                  <div className="extra-section">
                    <h2>Dodatkowe udogodnienia</h2>
                    <div className="extra-option-grid amenities-option-grid">
                      {visibleAmenityOptions.map((option) => (
                        <label className={selectedAmenities.includes(option.label) ? 'selected' : ''} key={option.label}>
                          <input type="checkbox" checked={selectedAmenities.includes(option.label)} onChange={() => toggleOption(option.label, setSelectedAmenities)} />
                          <Icon name={option.icon} />
                          <span>{option.label}</span>
                        </label>
                      ))}
                    </div>

                    {isSaleHouse && (
                      <div className="extra-details-grid" style={{ marginTop: 12 }}>
                        {selectedAmenities.includes('Ogrodzenie') && <AddSelect label="Ogrodzenie" value={extraInfo.fencing} options={extraInfoSelectOptions.fencing} onChange={(value) => updateExtraInfo('fencing', value)} />}
                      </div>
                    )}

                    {isRentTransaction && amenityBillingEntries.length > 0 && (
                      <div className="media-billing-panel">
                        <h3>Sposób rozliczenia udogodnień</h3>
                        <div className="media-billing-grid">
                          {amenityBillingEntries.map((item) => (
                            <label className="media-billing-row" key={item.key}>
                              <span>{item.label}</span>
                              <div className="media-billing-controls">
                                <select
                                  value={extraInfo.mediaBilling[item.key] ?? item.options[0]}
                                  onChange={(event) => updateMediaBilling(item.key, event.target.value)}
                                >
                                  {item.options.map((option) => (
                                    <option key={`${item.key}-${option}`} value={option}>{option}</option>
                                  ))}
                                </select>
                                {billingRequiresAmount(extraInfo.mediaBilling[item.key] ?? item.options[0]) && (
                                  <input
                                    type="text"
                                    inputMode="decimal"
                                    placeholder={(extraInfo.mediaBilling[item.key] ?? item.options[0]).includes('/mies.') ? 'Kwota / mies. (zł)' : 'Kwota (zł)'}
                                    value={extraInfo.mediaBillingAmounts[item.key] ?? ''}
                                    onChange={(event) => updateMediaBillingAmount(item.key, event.target.value)}
                                  />
                                )}
                              </div>
                            </label>
                          ))}
                        </div>
                      </div>
                    )}
                  </div>

                  <div className="add-location-actions extra-actions">
                    <button className="back-step-button" type="button" onClick={() => goToStep(2)}>
                      Wstecz
                    </button>
                    <button className="next-step-button" type="button" onClick={() => goToStep(4)}>
                      Dalej: Podgląd ogłoszenia
                      <Icon name="arrow" />
                    </button>
                  </div>
                </section>
              </>
            )}

            {isFinalPreviewStep && (
              <>
                <div className="add-title add-step-title">
                  <h1>Podgląd ogłoszenia</h1>
                  <p>Sprawdź, jak będzie wyglądać Twoje ogłoszenie.</p>
                </div>

                <section className="add-form-card final-preview-card">
                  <div className="final-preview-main">
                    <div className="final-gallery">
                      <span className="free-listing-badge">Ogłoszenie za darmo</span>
                      <div className="final-main-photo" style={{ backgroundImage: `url(${activeFinalImage})` }}>
                        <button type="button" onClick={showPreviousFinalPhoto} aria-label="Poprzednie zdjęcie">‹</button>
                        <button type="button" onClick={showNextFinalPhoto} aria-label="Następne zdjęcie">›</button>
                        <span>{finalGalleryIndex + 1}/{finalGalleryImages.length}</span>
                      </div>
                      <div className="final-thumbnails">
                        {finalGalleryImages.map((image, index) => (
                          <button className={index === finalGalleryIndex ? 'active' : ''} type="button" key={`${image}-${index}`} onClick={() => setFinalGalleryIndex(index)} style={{ backgroundImage: `url(${image})` }} aria-label={`Zdjęcie ${index + 1}${index === 0 ? ', zdjęcie główne' : ''}`}>
                            {index === 0 && <span>Główne</span>}
                          </button>
                        ))}
                      </div>
                    </div>

                    <div className="final-preview-info">
                      <h2>{previewTitle}</h2>
                      <p className="final-location">{previewLocation}</p>
                      <strong>
                        {isRentTransaction ? formattedMonthlyPrice : formattedPrice}{' '}
                        {!isRentTransaction && <small>{formattedPricePerMeter}</small>}
                      </strong>
                      {isRentTransaction && <span className="final-rent-deposit">{formattedDeposit}</span>}
                      <div className="final-meta-row">
                        {previewMetaItems.slice(0, 4).map((item) => (
                          <span key={item.label}><Icon name={item.icon} /> <small>{item.label}</small></span>
                        ))}
                      </div>

                      <div className="final-description-block">
                        <h3>Opis ogłoszenia</h3>
                        <p>{listingDetails.description.trim() || 'Opisz nieruchomość, jej lokalizację i najważniejsze atuty. Ten tekst pojawi się w pełnym ogłoszeniu.'}</p>
                      </div>

                      {hasVirtualTour && (
                        <div className="final-virtual-tour-block">
                          <h3>Wirtualny spacer 3D</h3>
                          <p>
                            {virtualTourEmbedPreview
                              ? `Podgląd osadzony (${virtualTourEmbedPreview.providerLabel}) będzie dostępny po publikacji ogłoszenia.`
                              : 'Link do spaceru zostanie udostępniony jako przycisk „Otwórz wirtualny spacer”.'}
                          </p>
                        </div>
                      )}

                      <div className="final-details-block">
                        <h3>Szczegóły</h3>
                        <dl>
                          {finalDetails.map((detail) => (
                            <div key={detail.label}>
                              <dt>{detail.label}:</dt>
                              <dd>{detail.value === 'Wybierz' ? 'Nie podano' : detail.value}</dd>
                            </div>
                          ))}
                        </dl>
                      </div>
                    </div>
                  </div>

                  <div className="final-features">
                    <h3>Wybrane udogodnienia</h3>
                    <div>
                      {(finalFeatureItems.length ? finalFeatureItems : [{ icon: 'document', label: 'Brak wybranych udogodnień' }]).map((feature) => (
                        <span key={feature.label}><Icon name={feature.icon} /> {feature.label}</span>
                      ))}
                    </div>
                  </div>

                  {publishState === 'success' ? (
                    <div className="publish-success-panel" role="status">
                      <div className="publish-success-icon"><Icon name="check" /></div>
                      <h3>Ogłoszenie zostało wysłane!</h3>
                      <p>
                        Trafiło do moderacji i oczekuje na zatwierdzenie przez administratora. Gdy zostanie
                        zaakceptowane, powiadomimy Cię, a ogłoszenie stanie się widoczne w zakładce{' '}
                        {listingDetails.transaction === 'Wynajem' ? 'Wynajmuję' : 'Kupuję'}. Do tego czasu
                        znajdziesz je w „Moich ogłoszeniach” ze statusem „Oczekuje”.
                      </p>
                      <div className="publish-success-actions">
                        <Link className="next-step-button" to={ROUTES.accountListings}>
                          Przejdź do moich ogłoszeń
                          <Icon name="arrow" />
                        </Link>
                        <Link className="back-step-button" to={ROUTES.home}>
                          Wróć do strony głównej
                        </Link>
                      </div>
                    </div>
                  ) : (
                    <>
                      {publishState === 'error' && (
                        <p className="publish-feedback error" role="alert">{publishError}</p>
                      )}
                      <div className="add-location-actions final-actions">
                        <button className="back-step-button" type="button" onClick={() => goToStep(3)}>
                          Wstecz
                        </button>
                        <button
                          className="next-step-button"
                          type="button"
                          onClick={handlePublish}
                          disabled={publishState === 'saving'}
                        >
                          {publishState === 'saving' ? 'Publikowanie…' : 'Opublikuj ogłoszenie'}
                          <Icon name="arrow" />
                        </button>
                      </div>
                    </>
                  )}
                </section>
              </>
            )}
          </div>

          <aside className="add-sidebar" aria-label="Podgląd i pomoc">
            {isFinalPreviewStep ? (
              <>
                <section className="add-side-card final-side-card">
                  <h2>Wskazówki</h2>
                  <div className="tips-list">
                    {[
                      { icon: 'camera', title: 'Zwróć uwagę na jakość zdjęć', text: 'Dobre zdjęcia przyciągają uwagę i zwiększają szanse na szybką sprzedaż.' },
                      { icon: 'document', title: 'Napisz dokładny opis', text: 'Szczegółowy opis nieruchomości buduje zaufanie i odpowiada na pytania kupujących.' },
                      { icon: 'shield', title: 'Podaj pełne informacje', text: 'Im więcej istotnych informacji, tym mniej pytań i szybsza decyzja kupującego.' },
                    ].map((tip) => (
                      <article key={tip.title}>
                        <span><Icon name={tip.icon} /></span>
                        <p><strong>{tip.title}</strong><small>{tip.text}</small></p>
                      </article>
                    ))}
                  </div>
                </section>

                <section className="add-side-card final-side-card">
                  <h2>Co dalej?</h2>
                  <div className="tips-list next-list">
                    {[
                      { icon: 'check', title: 'Opublikuj ogłoszenie', text: 'Twoje ogłoszenie będzie widoczne dla tysięcy użytkowników.' },
                      { icon: 'message', title: 'Odbieraj wiadomości', text: 'Zainteresowani kupujący skontaktują się z Tobą przez formularz lub telefon.' },
                      { icon: 'gear', title: 'Zarządzaj ogłoszeniem', text: 'W każdej chwili możesz edytować lub usunąć swoje ogłoszenie.' },
                    ].map((tip) => (
                      <article key={tip.title}>
                        <span><Icon name={tip.icon} /></span>
                        <p><strong>{tip.title}</strong><small>{tip.text}</small></p>
                      </article>
                    ))}
                  </div>
                </section>

                <section className="free-publish-card">
                  <span><Icon name="shield" /></span>
                  <p><strong>Ogłoszenie za darmo</strong><small>Dodajesz ogłoszenie całkowicie za darmo. Bez ukrytych opłat i prowizji.</small></p>
                </section>
              </>
            ) : (
              <>
            <section className="listing-preview-card">
              <h2>Podgląd ogłoszenia</h2>
              <div
                className={`preview-image ${showListingPreviewDetails ? 'photos-preview-image' : ''}`}
                style={{
                  backgroundImage: `url(${mainPhoto?.url ?? heroImage})`,
                  backgroundPosition: mainPhoto ? 'center' : undefined,
                  backgroundSize: mainPhoto ? 'cover' : undefined,
                }}
              >
                <span><Icon name="camera" /> {showListingPreviewDetails ? `${Math.max(photos.length, 1)}/20` : '1/8'}</span>
              </div>
              <div className="preview-body">
                <p>{previewTitle}</p>
                <strong>
                  {isRentTransaction ? formattedMonthlyPrice : formattedPrice}{' '}
                  {!isRentTransaction && <small>{formattedPricePerMeter}</small>}
                </strong>
                {isRentTransaction && <span className="preview-rent-deposit">{formattedDeposit}</span>}
                {showListingPreviewDetails ? (
                  <>
                    <span><Icon name="pin" /> {previewLocation}</span>
                    <div className="preview-meta preview-meta-icons">
                      {previewMetaItems.map((item) => (
                        <span key={item.label}><Icon name={item.icon} /> {item.label}</span>
                      ))}
                    </div>
                  </>
                ) : (
                  <>
                    <div className="preview-meta">
                      <span>{listingDetails.propertyType}</span>
                      <span>{previewArea}</span>
                      {showRoomFields && <span>{previewRooms}</span>}
                      {showFlatFields && <span>{previewFloor}</span>}
                    </div>
                    {isLocationStep && <span><Icon name="pin" /> {previewLocation}</span>}
                    <span><Icon name="clock" /> Dzisiaj</span>
                  </>
                )}
              </div>
            </section>

            <section className="add-side-card">
              <h2>Wskazówki</h2>
              <div className="tips-list">
                {(isPhotosStep ? [
                  { icon: 'camera', title: 'Dodaj zdjęcia wszystkich pomieszczeń', text: 'Pokaż salon, kuchnię, sypialnię, łazienkę i balkon.' },
                  { icon: 'spark', title: 'Pokaż nieruchomość w jasnym świetle', text: 'Najlepiej fotografuj w dzień, przy naturalnym świetle.' },
                  { icon: 'shield', title: 'Zrób zdjęcia w porządku', text: 'Usuń zbędne rzeczy i pokaż czystą, uporządkowaną przestrzeń.' },
                  { icon: 'star', title: 'Pierwsze zdjęcie będzie zdjęciem głównym', text: 'Wybierz najlepsze ujęcie, bo pojawi się jako miniatura ogłoszenia.' },
                ] : isExtraInfoStep ? extraInfoTips : listingTips).map((tip) => (
                  <article key={tip.title}>
                    <span>
                      <Icon name={tip.icon} />
                    </span>
                    <p>
                      <strong>{tip.title}</strong>
                      <small>{tip.text}</small>
                    </p>
                  </article>
                ))}
              </div>
            </section>

            <section className="add-side-card help-card">
              <h2>Potrzebujesz pomocy?</h2>
              <p>Skontaktuj się z nami, chętnie pomożemy.</p>
              <div>
                <span><Icon name="phone" /></span>
                <p>
                  <strong>+48 123 456 789</strong>
                  <small>Pon. - Pt. 9:00 - 17:00</small>
                </p>
              </div>
              <div>
                <span><Icon name="mail" /></span>
                <p>
                  <strong>pomoc@mieszko.pl</strong>
                  <small>Odpowiadamy w ciągu 24h</small>
                </p>
              </div>
              <button type="button">Najczęściej zadawane pytania</button>
            </section>
              </>
            )}
          </aside>
        </div>

        {!isFinalPreviewStep && <section className="add-footer-benefits">
          <h2>Dlaczego warto dodać ogłoszenie na mieszko.pl?</h2>
          <div>
            {addFooterBenefits.map((benefit) => (
              <article key={benefit.title}>
                <span>
                  <Icon name={benefit.icon} />
                </span>
                <p>
                  <strong>{benefit.title}</strong>
                  <small>{benefit.text}</small>
                </p>
              </article>
            ))}
          </div>
        </section>}
      </div>
    </section>
  );
}

const buyingProcessSteps = [
  { icon: 'search', title: 'Analiza potrzeb i możliwości', text: 'Określ, po co kupujesz i na co Cię stać.' },
  { icon: 'calculator', title: 'Określenie budżetu', text: 'Policz cenę plus wszystkie koszty dodatkowe.' },
  { icon: 'chart', title: 'Sprawdzenie zdolności', text: 'Zbadaj zdolność kredytową w kilku bankach.' },
  { icon: 'pin', title: 'Wybór i analiza lokalizacji', text: 'Sprawdź okolicę, dojazdy i plany zagospodarowania.' },
  { icon: 'eye', title: 'Oglądanie mieszkań', text: 'Odwiedź kilka ofert i porównaj je na spokojnie.' },
  { icon: 'group', title: 'Negocjacja ceny', text: 'Ustal realną wartość i wynegocjuj warunki.' },
  { icon: 'calendar', title: 'Rezerwacja mieszkania', text: 'Zabezpiecz ofertę umową rezerwacyjną.' },
  { icon: 'document', title: 'Umowa przedwstępna', text: 'Spisz warunki i terminy u notariusza lub cywilnie.' },
  { icon: 'building', title: 'Kredyt hipoteczny', text: 'Złóż wnioski i wybierz najlepszą ofertę.' },
  { icon: 'scale', title: 'Akt notarialny', text: 'Podpisz umowę przenoszącą własność.' },
  { icon: 'key', title: 'Odbiór mieszkania', text: 'Sprawdź stan techniczny i spisz protokół.' },
  { icon: 'house', title: 'Zameldowanie i formalności', text: 'Przepisz media, ubezpiecz i zamelduj się.' },
];

const buyingToc = [
  { id: 'start', label: 'Od czego zacząć?' },
  { id: 'budzet', label: 'Jak określić budżet?' },
  { id: 'gotowka-kredyt', label: 'Gotówka czy kredyt?' },
  { id: 'zdolnosc', label: 'Zdolność kredytowa' },
  { id: 'rynek', label: 'Rynek pierwotny czy wtórny?' },
  { id: 'lokalizacja', label: 'Wybór lokalizacji' },
  { id: 'ogladanie', label: 'Jak oglądać mieszkanie?' },
  { id: 'stan-techniczny', label: 'Stan techniczny' },
  { id: 'ksiega-wieczysta', label: 'Księga wieczysta' },
  { id: 'negocjacje', label: 'Negocjacja ceny' },
  { id: 'umowa-przedwstepna', label: 'Umowa przedwstępna' },
  { id: 'akt-notarialny', label: 'Akt notarialny' },
  { id: 'odbior', label: 'Odbiór mieszkania' },
  { id: 'klucze', label: 'Przekazanie kluczy' },
  { id: 'bledy', label: 'Najczęstsze błędy' },
  { id: 'faq', label: 'FAQ' },
  { id: 'podsumowanie', label: 'Podsumowanie' },
];

const buyingCostRows = [
  { name: 'Wkład własny', who: 'Kupujący', value: 'zwykle 10–20% ceny', note: 'Im wyższy, tym niższa rata i lepsze warunki kredytu.' },
  { name: 'Podatek PCC (rynek wtórny)', who: 'Kupujący', value: '2% wartości', note: 'Zakup pierwszego mieszkania bywa zwolniony z PCC.' },
  { name: 'Taksa notarialna', who: 'Kupujący', value: 'zależna od ceny', note: 'Stawki maksymalne są ustalone rozporządzeniem, do negocjacji.' },
  { name: 'Wpis do księgi wieczystej', who: 'Kupujący', value: '200 zł + 200 zł', note: 'Wpis własności oraz ewentualnej hipoteki.' },
  { name: 'Prowizja pośrednika', who: 'Zależnie od umowy', value: 'zwykle 1–3%', note: 'Tylko jeśli korzystasz z biura nieruchomości.' },
  { name: 'Koszty kredytu', who: 'Kredytobiorca', value: 'prowizja, wycena, ubezpieczenia', note: 'Doliczane do finansowania zakupu z banku.' },
];

const buyingChecklist = [
  'Przeczytaj aktualny odpis z księgi wieczystej i sprawdź dział III i IV.',
  'Poproś o zaświadczenie o braku zaległości w czynszu i mediach.',
  'Sprawdź, kto figuruje jako właściciel i czy zgadza się ze sprzedającym.',
  'Zapytaj o zaległości i plany remontowe wspólnoty lub spółdzielni.',
  'Obejrzyj mieszkanie za dnia i wieczorem, sprawdź nasłonecznienie.',
  'Skontroluj instalacje: prąd, wodę, ogrzewanie, wentylację i wilgoć.',
  'Ustal, co dokładnie wchodzi w cenę (meble, kuchnia, miejsce postojowe).',
  'Zweryfikuj wysokość czynszu administracyjnego i co obejmuje.',
  'Sprawdź plan zagospodarowania i co może powstać w okolicy.',
  'Policz pełny koszt zakupu z podatkami i kredytem, nie tylko cenę.',
];

const buyingMistakes = [
  { title: 'Liczenie tylko ceny mieszkania', text: 'Pomijanie podatków, taksy notarialnej, kosztów kredytu i wykończenia potrafi podnieść budżet o kilkanaście procent.' },
  { title: 'Pominięcie księgi wieczystej', text: 'Brak sprawdzenia działu III i IV grozi zakupem mieszkania z hipoteką, służebnością lub roszczeniem osoby trzeciej.' },
  { title: 'Zakup pod wpływem emocji', text: 'Decyzja po jednym oglądaniu i „bo ktoś inny też jest zainteresowany” to najczęstsza droga do przepłacenia.' },
  { title: 'Brak zapasu w budżecie', text: 'Kredyt na maksimum zdolności nie zostawia poduszki na wzrost rat, remont czy nagłe wydatki.' },
  { title: 'Zaliczka zamiast zadatku', text: 'Przy zaliczce, jeśli druga strona się wycofa, trudniej dochodzić rekompensaty niż przy zadatku.' },
  { title: 'Zignorowanie stanu technicznego', text: 'Ukryta wilgoć, zła instalacja elektryczna czy stare piony to koszty, które ujawniają się dopiero po zakupie.' },
];

const buyingFaq = [
  {
    q: 'Ile własnych pieniędzy potrzebuję na start?',
    a: 'Do kredytu banki oczekują zwykle wkładu własnego na poziomie 10–20% ceny. Do tego dochodzą koszty transakcyjne: podatek, taksa notarialna, wpisy do księgi wieczystej i ewentualna prowizja pośrednika. Bezpiecznie jest założyć, że oprócz wkładu własnego potrzebujesz jeszcze kilku procent ceny na same formalności.',
  },
  {
    q: 'Czy lepiej kupić od dewelopera czy z rynku wtórnego?',
    a: 'To zależy od Twoich priorytetów. Nowe mieszkanie od dewelopera bywa energooszczędne i objęte gwarancją, ale często wymaga wykończenia i czekania na odbiór. Rynek wtórny to gotowe do zamieszkania lokale w ukształtowanych dzielnicach, za to trzeba dokładniej sprawdzić stan techniczny i stan prawny.',
  },
  {
    q: 'Czy przy zakupie pierwszego mieszkania zapłacę podatek PCC?',
    a: 'Przy zakupie z rynku wtórnego standardowo obowiązuje podatek 2% od wartości. Dla osób kupujących swoje pierwsze mieszkanie lub dom przewidziano jednak zwolnienie z tego podatku. Zawsze potwierdź aktualne warunki u notariusza, bo to on nalicza i odprowadza PCC.',
  },
  {
    q: 'Jak długo trwa cały proces zakupu?',
    a: 'Zakup za gotówkę można sfinalizować nawet w kilka tygodni. Z kredytem hipotecznym realny czas to zwykle 1,5–3 miesiące, bo dochodzi analiza wniosku, wycena nieruchomości i decyzja banku. Warto ten czas uwzględnić w umowie przedwstępnej.',
  },
  {
    q: 'Co daje umowa przedwstępna?',
    a: 'Zabezpiecza obie strony: ustala cenę, termin i warunki finalnej transakcji. Najczęściej towarzyszy jej zadatek. Jeśli od umowy odstąpi kupujący, traci zadatek; jeśli sprzedający, zwraca go w podwójnej wysokości. To silniejsze zabezpieczenie niż zwykła zaliczka.',
  },
  {
    q: 'Czy muszę korzystać z pośrednika?',
    a: 'Nie ma takiego obowiązku. Dobry pośrednik oszczędza czas i pomaga w formalnościach, ale pobiera prowizję. Jeśli kupujesz samodzielnie, tym bardziej zadbaj o weryfikację stanu prawnego i technicznego oraz o dobrze napisane umowy.',
  },
];

async function downloadBuyingChecklistPdf() {
  const unwrapModuleDefault = <T,>(module: T | { default: T }): T =>
    'default' in Object(module) ? (module as { default: T }).default : (module as T);
  const downloadBlob = (blob: Blob, fileName: string) => {
    const objectUrl = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = objectUrl;
    link.download = fileName;
    link.rel = 'noopener';
    document.body.appendChild(link);
    link.click();
    link.remove();
    window.setTimeout(() => URL.revokeObjectURL(objectUrl), 1000);
  };

  type PdfMakeRuntime = {
    addFontContainer: (fontContainer: unknown) => void;
    createPdf: (definition: unknown) => { getBlob: () => Promise<Blob> };
  };

  let pdfMake: PdfMakeRuntime;
  try {
    const pdfMakeModule = await import('pdfmake/build/pdfmake.js');
    const robotoFontModule = await import('pdfmake/build/fonts/Roboto.js');
    pdfMake = unwrapModuleDefault(pdfMakeModule as unknown as PdfMakeRuntime | { default: PdfMakeRuntime });
    pdfMake.addFontContainer(unwrapModuleDefault(robotoFontModule as unknown as { default: unknown }));
  } catch (error) {
    console.error('Nie udało się załadować generatora PDF', error);
    window.alert('Nie udało się przygotować pliku PDF. Odśwież stronę i spróbuj ponownie.');
    return;
  }

  const docDefinition = {
    pageSize: 'A4',
    pageMargins: [48, 48, 48, 48],
    defaultStyle: { font: 'Roboto', color: '#14223a', fontSize: 11, lineHeight: 1.35 },
    content: [
      { text: 'PolskaLokalnie', color: '#0b9f5c', fontSize: 12, bold: true, margin: [0, 0, 0, 2] },
      { text: 'Lista kontrolna przy zakupie mieszkania', fontSize: 20, bold: true, margin: [0, 0, 0, 4] },
      { text: '10 rzeczy, które warto sprawdzić przed podpisaniem umowy', color: '#526171', margin: [0, 0, 0, 18] },
      {
        ol: buyingChecklist.map((item) => ({ text: item, margin: [0, 0, 0, 6] })),
      },
      {
        text: 'Materiał informacyjny. Nie stanowi porady prawnej ani podatkowej – w razie wątpliwości skonsultuj się z notariuszem lub doradcą.',
        color: '#8793a3',
        fontSize: 9,
        margin: [0, 22, 0, 0],
      },
    ],
  };

  try {
    const blob = await pdfMake.createPdf(docDefinition).getBlob();
    downloadBlob(blob, 'lista-kontrolna-zakup-mieszkania.pdf');
  } catch (error) {
    console.error('Nie udało się wygenerować PDF', error);
    window.alert('Nie udało się wygenerować pliku PDF. Spróbuj ponownie.');
  }
}

function GuideBuyingPage() {
  const navigate = useNavigate();
  const scrollToId = (id: string) => {
    const target = document.getElementById(id);
    if (target) {
      target.scrollIntoView({ behavior: 'smooth', block: 'start' });
    }
  };

  return (
    <section className="guide-article" aria-label="Poradnik: kupno mieszkania">
      <header className="guide-hero">
        <img src={heroImage} alt="" />
        <div className="guide-hero-shade" />
        <div className="guides-container guide-hero-inner">
          <Link className="guide-back" to={ROUTES.guides}>
            <Icon name="arrow" />
            <span>Wróć do poradników</span>
          </Link>
          <span className="guide-hero-badge">
            <Icon name="house" />
            Kupno mieszkania
          </span>
          <h1>Jak kupić mieszkanie krok po kroku</h1>
          <p className="guide-hero-lead">
            Kompletny, uczciwy przewodnik po całym procesie zakupu – od pierwszej decyzji, przez budżet,
            kredyt i formalności, aż po odbiór kluczy. Prostym językiem, z listami kontrolnymi i praktycznymi
            wskazówkami, które pomogą Ci uniknąć najczęstszych błędów.
          </p>
          <div className="guide-hero-chips">
            <span><Icon name="check" /> Aktualna wiedza na 2026 rok</span>
            <span><Icon name="shield" /> Oparte na sprawdzonych zasadach</span>
            <span><Icon name="list" /> Listy kontrolne i PDF</span>
            <span><Icon name="calculator" /> Kalkulatory kosztów</span>
          </div>
        </div>
      </header>

      <div className="guides-container guide-article-layout">
        <aside className="guide-toc" aria-label="Spis treści">
          <div className="guide-toc-inner">
            <h2>Spis treści</h2>
            <ol>
              {buyingToc.map((item, index) => (
                <li key={item.id}>
                  <button onClick={() => scrollToId(item.id)}>
                    <span>{index + 1}</span>
                    {item.label}
                  </button>
                </li>
              ))}
            </ol>
          </div>
        </aside>

        <div className="guide-body">
          <section className="guide-process" aria-label="Proces zakupu w 12 krokach">
            <div className="guide-block-head">
              <h2>Proces zakupu mieszkania w 12 krokach</h2>
              <p>Cała droga w skrócie. Poniżej rozwijamy każdy etap dokładnie.</p>
            </div>
            <div className="guide-process-grid">
              {buyingProcessSteps.map((step, index) => (
                <article key={step.title} className="guide-process-card">
                  <span className="guide-process-num">{index + 1}</span>
                  <span className="guide-process-icon"><Icon name={step.icon} /></span>
                  <strong>{step.title}</strong>
                  <small>{step.text}</small>
                </article>
              ))}
            </div>
          </section>

          <article className="guide-chapter" id="start">
            <span className="guide-chapter-index">01</span>
            <h2>Od czego zacząć zakup mieszkania?</h2>
            <p>
              Zanim zaczniesz przeglądać oferty, odpowiedz sobie na kilka pytań. To one, a nie ładne zdjęcia,
              zdecydują o trafności zakupu. Ustal, czy kupujesz dla siebie, dla rodziny, czy pod inwestycję,
              jak długo planujesz tu mieszkać i co jest dla Ciebie nienegocjowalne – metraż, liczba pokoi,
              piętro, winda, balkon czy miejsce postojowe.
            </p>
            <p>
              Rozdziel potrzeby od zachcianek. Potrzeba to np. druga sypialnia przy powiększającej się rodzinie.
              Zachcianka to konkretny kolor kuchni, który i tak zmienisz. Im lepiej rozpoznasz swoje realne potrzeby,
              tym mniej ofert będziesz musiał obejrzeć i tym rzadziej dasz się ponieść emocjom.
            </p>
            <div className="guide-callout guide-callout-tip">
              <Icon name="spark" />
              <div>
                <strong>Wskazówka</strong>
                <p>Wypisz na kartce trzy kryteria, których nie odpuścisz, i trzy, które są mile widziane. Wracaj do tej listy przy każdym oglądaniu – to najprostsza ochrona przed impulsywną decyzją.</p>
              </div>
            </div>
          </article>

          <article className="guide-chapter" id="budzet">
            <span className="guide-chapter-index">02</span>
            <h2>Jak określić budżet?</h2>
            <p>
              Budżet to nie tylko cena mieszkania. To cena powiększona o wszystkie koszty transakcyjne oraz o to,
              co wydasz zaraz po zakupie. Zsumuj cenę, podatki, taksę notarialną, wpisy do księgi wieczystej,
              ewentualną prowizję pośrednika i koszty kredytu. Dopiero ta kwota pokazuje, na co naprawdę Cię stać.
            </p>
            <div className="guide-table-wrap">
              <table className="guide-table">
                <thead>
                  <tr>
                    <th>Koszt</th>
                    <th>Kto ponosi</th>
                    <th>Ile (orientacyjnie)</th>
                    <th>Uwagi</th>
                  </tr>
                </thead>
                <tbody>
                  {buyingCostRows.map((row) => (
                    <tr key={row.name}>
                      <td data-label="Koszt"><strong>{row.name}</strong></td>
                      <td data-label="Kto ponosi">{row.who}</td>
                      <td data-label="Ile">{row.value}</td>
                      <td data-label="Uwagi">{row.note}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
            <div className="guide-callout guide-callout-warn">
              <Icon name="warning" />
              <div>
                <strong>Zostaw zapas</strong>
                <p>Nie planuj zakupu na granicy możliwości. Poduszka finansowa na kilka rat, drobny remont i nieprzewidziane wydatki to nie luksus, tylko element bezpiecznego budżetu.</p>
              </div>
            </div>
          </article>

          <article className="guide-chapter" id="gotowka-kredyt">
            <span className="guide-chapter-index">03</span>
            <h2>Gotówka czy kredyt hipoteczny?</h2>
            <p>
              Zakup za gotówkę jest szybszy, prostszy i pozwala mocniej negocjować, bo sprzedający nie czeka na
              decyzję banku. Nie zawsze jednak warto zamrażać wszystkie oszczędności w jednej nieruchomości –
              tracisz płynność i poduszkę bezpieczeństwa.
            </p>
            <p>
              Kredyt hipoteczny rozkłada koszt w czasie i pozwala kupić wcześniej, ale oznacza wieloletnie
              zobowiązanie, odsetki i zależność od zmian stóp procentowych. Zanim zdecydujesz, porównaj oferty
              kilku banków i policz całkowity koszt kredytu, a nie tylko wysokość raty.
            </p>
            <div className="guide-two-col">
              <div className="guide-pro">
                <h4><Icon name="check" /> Gotówka</h4>
                <ul>
                  <li>Szybka i prosta transakcja</li>
                  <li>Silniejsza pozycja w negocjacjach</li>
                  <li>Brak odsetek i rat</li>
                  <li>Uwaga: mniejsza płynność finansowa</li>
                </ul>
              </div>
              <div className="guide-con">
                <h4><Icon name="credit-card" /> Kredyt hipoteczny</h4>
                <ul>
                  <li>Kupujesz, nie mając całej kwoty</li>
                  <li>Oszczędności zostają jako rezerwa</li>
                  <li>Uwaga: całkowity koszt rośnie o odsetki</li>
                  <li>Uwaga: wieloletnie zobowiązanie</li>
                </ul>
              </div>
            </div>
          </article>

          <article className="guide-chapter" id="zdolnosc">
            <span className="guide-chapter-index">04</span>
            <h2>Jak obliczyć zdolność kredytową?</h2>
            <p>
              Zdolność kredytowa to maksymalna kwota, jaką bank jest gotów Ci pożyczyć. Wpływają na nią głównie:
              wysokość i stabilność dochodów, forma zatrudnienia, liczba osób na utrzymaniu, inne zobowiązania
              (raty, karty, limity) oraz historia w BIK. Banki liczą też bufor na wypadek wzrostu stóp, dlatego
              maksymalna kwota bywa niższa, niż wynikałoby z samej raty.
            </p>
            <p>
              Każdy bank liczy zdolność inaczej, więc warto sprawdzić kilka ofert – różnice bywają znaczne.
              Zanim złożysz wnioski, uporządkuj finanse: spłać drobne zobowiązania, zamknij nieużywane karty
              i limity, zadbaj o pozytywną historię kredytową. To realnie podnosi zdolność.
            </p>
            <div className="guide-callout guide-callout-tip">
              <Icon name="calculator" />
              <div>
                <strong>Policz orientacyjnie</strong>
                <p>Skorzystaj z naszych kalkulatorów po prawej stronie, aby oszacować budżet i porównać koszty. Ostateczną zdolność zawsze potwierdza bank na podstawie dokumentów.</p>
              </div>
            </div>
          </article>

          <article className="guide-chapter" id="rynek">
            <span className="guide-chapter-index">05</span>
            <h2>Rynek pierwotny czy wtórny?</h2>
            <p>
              Rynek pierwotny to zakup od dewelopera – najczęściej nowe, energooszczędne mieszkanie, często jeszcze
              w budowie i do wykończenia. Rynek wtórny to zakup od dotychczasowego właściciela – zwykle mieszkanie
              gotowe do zamieszkania, w ukształtowanej okolicy. Oba mają swoje zalety; wybór zależy od tego, czy
              wolisz nowe i „pod siebie”, czy gotowe i sprawdzone.
            </p>
            <div className="guide-two-col">
              <div className="guide-market">
                <h4><Icon name="building" /> Rynek pierwotny</h4>
                <ul>
                  <li>Nowy standard i niższe koszty eksploatacji</li>
                  <li>Możliwość aranżacji od podstaw</li>
                  <li>Gwarancja i rękojmia dewelopera</li>
                  <li>Uwaga: często konieczne wykończenie i czekanie na odbiór</li>
                </ul>
              </div>
              <div className="guide-market">
                <h4><Icon name="house" /> Rynek wtórny</h4>
                <ul>
                  <li>Gotowe do zamieszkania od zaraz</li>
                  <li>Sprawdzona lokalizacja i sąsiedztwo</li>
                  <li>Możliwość zobaczenia realnego stanu</li>
                  <li>Uwaga: dokładniej sprawdź stan techniczny i prawny</li>
                </ul>
              </div>
            </div>
          </article>

          <article className="guide-chapter" id="lokalizacja">
            <span className="guide-chapter-index">06</span>
            <h2>Jak wybrać lokalizację?</h2>
            <p>
              Lokalizacji nie zmienisz remontem, dlatego to jedno z najważniejszych kryteriów. Sprawdź dojazd do
              pracy i szkoły, dostęp do komunikacji, sklepów, przychodni i terenów zielonych. Odwiedź okolicę o
              różnych porach dnia – rano, po południu i wieczorem – żeby poznać hałas, ruch i realny klimat miejsca.
            </p>
            <p>
              Zajrzyj do miejscowego planu zagospodarowania przestrzennego, żeby wiedzieć, co może powstać w
              sąsiedztwie – park czy ruchliwa droga to duża różnica dla komfortu i przyszłej wartości. Zwróć uwagę
              na kierunki świata i nasłonecznienie oraz na to, czy okolica się rozwija, czy raczej podupada.
            </p>
          </article>

          <article className="guide-chapter" id="ogladanie">
            <span className="guide-chapter-index">07</span>
            <h2>Jak oglądać mieszkanie?</h2>
            <p>
              Nie kupuj po jednym oglądaniu i nie oglądaj tylko przy pełnym słońcu. Przyjdź kilka razy, także
              wieczorem, i nie bój się zadawać pytań: o wysokość czynszu i co obejmuje, o koszty ogrzewania, o wiek
              instalacji, o plany remontowe budynku i o powód sprzedaży. Rób zdjęcia i notatki, bo po obejrzeniu
              kilku ofert łatwo je pomylić.
            </p>
            <p>
              Zwróć uwagę na to, czego nie widać na pierwszy rzut oka: ślady wilgoci w narożnikach i pod oknami,
              stan okien i drzwi, ciśnienie wody, działanie wentylacji, wygłuszenie między mieszkaniami. Sprawdź też
              części wspólne – klatkę, windę, piwnicę i otoczenie budynku mówią wiele o tym, jak zarządzana jest
              nieruchomość.
            </p>
          </article>

          <article className="guide-chapter" id="stan-techniczny">
            <span className="guide-chapter-index">08</span>
            <h2>Jak sprawdzić stan techniczny?</h2>
            <p>
              Stan techniczny decyduje o tym, ile dołożysz po zakupie. Najważniejsze punkty to instalacja
              elektryczna (czy jest bezpieczna i o odpowiedniej mocy), instalacja wodno-kanalizacyjna i piony,
              ogrzewanie, wentylacja oraz izolacja i ślady wilgoci lub grzyba. W starszych budynkach zwróć uwagę na
              wiek instalacji i okien – ich wymiana bywa kosztowna.
            </p>
            <div className="guide-callout guide-callout-tip">
              <Icon name="hammer" />
              <div>
                <strong>Rozważ fachowca</strong>
                <p>Przy droższym zakupie lub starszym mieszkaniu warto zabrać na oględziny osobę znającą się na budownictwie. Koszt takiej konsultacji jest niewielki wobec potencjalnych ukrytych wad.</p>
              </div>
            </div>
          </article>

          <article className="guide-chapter" id="ksiega-wieczysta">
            <span className="guide-chapter-index">09</span>
            <h2>Jak sprawdzić księgę wieczystą?</h2>
            <p>
              Księga wieczysta to dokument, który pokazuje stan prawny nieruchomości. Odpis możesz sprawdzić online,
              znając numer księgi. Zwróć uwagę na cztery działy: oznaczenie nieruchomości, właściciela, prawa i
              ograniczenia oraz hipoteki. To podstawowa ochrona przed zakupem mieszkania z niespodziankami.
            </p>
            <ul className="guide-list">
              <li><strong>Dział I</strong> – co to za nieruchomość: adres, metraż, pomieszczenia przynależne.</li>
              <li><strong>Dział II</strong> – kto jest właścicielem. Musi zgadzać się z osobą sprzedającą.</li>
              <li><strong>Dział III</strong> – prawa, ograniczenia, służebności i roszczenia osób trzecich.</li>
              <li><strong>Dział IV</strong> – hipoteki. Jeśli jest, ustal z bankiem i notariuszem sposób jej wykreślenia.</li>
            </ul>
            <div className="guide-callout guide-callout-warn">
              <Icon name="shield" />
              <div>
                <strong>Nie pomijaj tego kroku</strong>
                <p>Wpis obciążający nieruchomość może przejść na Ciebie razem z zakupem. Sprawdzenie księgi to kilka minut, a chroni przed poważnymi problemami.</p>
              </div>
            </div>
          </article>

          <article className="guide-chapter" id="negocjacje">
            <span className="guide-chapter-index">10</span>
            <h2>Jak negocjować cenę?</h2>
            <p>
              Negocjacje zacznij od wiedzy. Sprawdź ceny podobnych mieszkań w okolicy, zobacz, jak długo oferta jest
              wystawiona i poznaj sytuację sprzedającego. Argumentem nie są emocje, tylko fakty: konieczne remonty,
              stan instalacji, wysoki czynsz czy niższe ceny porównywalnych lokali.
            </p>
            <p>
              Bądź konkretny i spokojny. Podaj kwotę, którą jesteś gotów zapłacić, wraz z uzasadnieniem, i miej
              wyznaczoną granicę, powyżej której rezygnujesz. Gotowość do szybkiej, pewnej transakcji – zwłaszcza za
              gotówkę – to często silniejszy argument niż sama presja na obniżkę.
            </p>
          </article>

          <article className="guide-chapter" id="umowa-przedwstepna">
            <span className="guide-chapter-index">11</span>
            <h2>Umowa przedwstępna</h2>
            <p>
              Umowa przedwstępna zabezpiecza ustalenia, zanim dojdzie do finalnej transakcji. Określa cenę, termin
              zawarcia umowy właściwej, stan mieszkania i warunki, np. uzyskanie kredytu. Można ją zawrzeć zwykłą
              formą pisemną lub u notariusza – forma notarialna daje silniejszą ochronę.
            </p>
            <div className="guide-callout guide-callout-info">
              <Icon name="document" />
              <div>
                <strong>Zadatek a zaliczka</strong>
                <p>Zadatek działa dyscyplinująco: jeśli od umowy odstąpi kupujący, traci zadatek; jeśli sprzedający – zwraca go w podwójnej wysokości. Zaliczka jest po prostu zwracana. Dla bezpieczeństwa transakcji częściej stosuje się zadatek.</p>
              </div>
            </div>
          </article>

          <article className="guide-chapter" id="akt-notarialny">
            <span className="guide-chapter-index">12</span>
            <h2>Akt notarialny</h2>
            <p>
              Przeniesienie własności mieszkania musi mieć formę aktu notarialnego. To notariusz sprawdza tożsamość
              stron, stan prawny nieruchomości i treść umowy, a następnie składa wniosek o wpis nowego właściciela do
              księgi wieczystej. Przy akcie płacisz taksę notarialną, podatek oraz opłaty sądowe.
            </p>
            <p>
              Przed podpisaniem dokładnie przeczytaj projekt aktu – ceny, terminy, sposób i datę wydania mieszkania,
              zapisy o zadatku i o wykreśleniu ewentualnej hipoteki. Jeśli czegoś nie rozumiesz, pytaj notariusza.
              To jego rola, żeby wyjaśnić Ci każdy zapis.
            </p>
          </article>

          <article className="guide-chapter" id="odbior">
            <span className="guide-chapter-index">13</span>
            <h2>Odbiór mieszkania</h2>
            <p>
              Odbiór to moment sprawdzenia, czy dostajesz mieszkanie w umówionym stanie. Spisz protokół zdawczo-odbiorczy:
              stany liczników (prąd, woda, gaz, ciepło), liczbę kluczy i pilotów oraz to, co zostaje w mieszkaniu.
              Przy nowym lokalu od dewelopera dokładnie sprawdź jakość wykończenia i zgłoś wszystkie usterki w protokole.
            </p>
            <div className="guide-callout guide-callout-tip">
              <Icon name="list" />
              <div>
                <strong>Wszystko na piśmie</strong>
                <p>Każdą usterkę i każdy stan licznika zapisz w protokole podpisanym przez obie strony. To Twój dowód na wypadek późniejszych rozliczeń i reklamacji.</p>
              </div>
            </div>
          </article>

          <article className="guide-chapter" id="klucze">
            <span className="guide-chapter-index">14</span>
            <h2>Przekazanie kluczy i formalności po zakupie</h2>
            <p>
              Po odbiorze zostają formalności, o których łatwo zapomnieć w euforii. Przepisz na siebie umowy na prąd,
              gaz, wodę i internet, zgłoś zmianę właściciela do wspólnoty lub spółdzielni, rozważ ubezpieczenie
              mieszkania i zamelduj się pod nowym adresem. Zachowaj wszystkie dokumenty zakupu w jednym miejscu.
            </p>
            <ul className="guide-list">
              <li>Przepisanie liczników i umów z dostawcami mediów.</li>
              <li>Zgłoszenie nowego właściciela do zarządcy budynku.</li>
              <li>Ubezpieczenie nieruchomości (a przy kredycie – często wymagane).</li>
              <li>Zameldowanie oraz aktualizacja adresu w ważnych instytucjach.</li>
            </ul>
          </article>

          <section className="guide-checklist" id="checklista">
            <div className="guide-block-head">
              <h2>Checklista – 10 rzeczy do sprawdzenia przed zakupem</h2>
              <p>Wydrukuj lub pobierz w PDF i miej przy sobie podczas oglądania.</p>
            </div>
            <div className="guide-checklist-grid">
              {buyingChecklist.map((item, index) => (
                <label className="guide-checklist-item" key={index}>
                  <input type="checkbox" />
                  <span className="guide-check-box"><Icon name="check" /></span>
                  <span>{item}</span>
                </label>
              ))}
            </div>
            <button className="guide-download-btn" onClick={downloadBuyingChecklistPdf}>
              <Icon name="document" />
              Pobierz listę kontrolną (PDF)
            </button>
          </section>

          <article className="guide-chapter" id="bledy">
            <span className="guide-chapter-index">15</span>
            <h2>Najczęstsze błędy kupujących</h2>
            <p>Większości kosztownych pomyłek da się uniknąć, jeśli wie się, gdzie najczęściej się pojawiają.</p>
            <div className="guide-mistakes">
              {buyingMistakes.map((mistake) => (
                <article key={mistake.title} className="guide-mistake">
                  <span className="guide-mistake-icon"><Icon name="warning" /></span>
                  <div>
                    <strong>{mistake.title}</strong>
                    <p>{mistake.text}</p>
                  </div>
                </article>
              ))}
            </div>
          </article>

          <article className="guide-chapter" id="faq">
            <span className="guide-chapter-index">16</span>
            <h2>Najczęściej zadawane pytania (FAQ)</h2>
            <div className="guide-faq">
              {buyingFaq.map((item) => (
                <details key={item.q} className="guide-faq-item">
                  <summary>
                    {item.q}
                    <Icon name="chevron" />
                  </summary>
                  <p>{item.a}</p>
                </details>
              ))}
            </div>
          </article>

          <article className="guide-chapter guide-summary" id="podsumowanie">
            <span className="guide-chapter-index">17</span>
            <h2>Podsumowanie</h2>
            <p>
              Zakup mieszkania to jedna z ważniejszych decyzji finansowych w życiu, ale rozłożona na etapy przestaje
              być przytłaczająca. Zacznij od potrzeb i budżetu, świadomie wybierz sposób finansowania, dokładnie
              sprawdź stan techniczny i prawny, a formalności potraktuj poważnie. Najlepszą ochroną są wiedza,
              spokój i pisemne ustalenia – emocje zostaw na moment odbioru kluczy.
            </p>
            <div className="guide-summary-note">
              <Icon name="warning" />
              <p>Ten poradnik ma charakter informacyjny i nie stanowi porady prawnej ani podatkowej. Szczegóły dotyczące podatków, opłat i umów potwierdzaj u notariusza, doradcy kredytowego lub prawnika.</p>
            </div>
          </article>

          <section className="guide-cta">
            <div>
              <h2>Gotowy, by znaleźć swoje mieszkanie?</h2>
              <p>Przeglądaj aktualne oferty dopasowane do Twoich potrzeb i budżetu.</p>
            </div>
            <Link to={ROUTES.buy}>
              Zobacz oferty mieszkań
              <Icon name="arrow" />
            </Link>
          </section>
        </div>

        <aside className="guide-aside" aria-label="Narzędzia i materiały">
          <section className="guide-aside-card">
            <h3><Icon name="calculator" /> Kalkulatory</h3>
            <div className="guide-aside-list">
              <Link to={ROUTES.creditCalculator}>
                <span><strong>Kalkulator zdolności kredytowej</strong><small>Sprawdź, na jaki kredyt Cię stać</small></span>
                <Icon name="chevron" />
              </Link>
              <Link to={ROUTES.valuation}>
                <span><strong>Kalkulator wyceny mieszkania</strong><small>Oszacuj wartość nieruchomości</small></span>
                <Icon name="chevron" />
              </Link>
              <Link to={ROUTES.priceHistory}>
                <span><strong>Historia cen</strong><small>Zobacz, jak zmieniały się ceny</small></span>
                <Icon name="chevron" />
              </Link>
              <Link to={ROUTES.districtRanking}>
                <span><strong>Ranking dzielnic</strong><small>Porównaj lokalizacje</small></span>
                <Icon name="chevron" />
              </Link>
            </div>
          </section>

          <section className="guide-aside-card">
            <h3><Icon name="document" /> Do pobrania</h3>
            <div className="guide-aside-downloads">
              <button onClick={downloadBuyingChecklistPdf}>
                <Icon name="document" />
                <span><strong>Lista kontrolna (PDF)</strong><small>10 rzeczy do sprawdzenia</small></span>
                <Icon name="upload" />
              </button>
            </div>
          </section>

          <section className="guide-aside-card guide-aside-mistakes">
            <h3><Icon name="warning" /> Najczęstsze błędy</h3>
            <ul>
              {buyingMistakes.slice(0, 5).map((mistake) => (
                <li key={mistake.title}>
                  <Icon name="warning" />
                  {mistake.title}
                </li>
              ))}
            </ul>
          </section>
        </aside>
      </div>
    </section>
  );
}

function GuidesPage() {
  const navigate = useNavigate();
  return (
    <section className="guides-page" aria-label="Poradniki">
      <section className="guides-hero">
        <img src={heroImage} alt="" />
        <div className="guides-hero-shade" />
        <div className="guides-container guides-hero-inner">
          <div>
            <h1>Centrum wiedzy o nieruchomościach</h1>
            <p>Praktyczne poradniki, analizy rynku i eksperckie wskazówki, które pomogą Ci podjąć najlepsze decyzje.</p>
            <div className="guides-search">
              <Icon name="search" />
              <input placeholder="O czym chcesz przeczytać?" />
              <button>Szukaj</button>
            </div>
            <div className="popular-links">
              <span>Popularne:</span>
              <Link to={ROUTES.creditCalculator}>Kredyt hipoteczny</Link>
              <Link to={ROUTES.rent}>Umowa najmu</Link>
              <Link to={ROUTES.sell}>Podatek od sprzedaży</Link>
              <Link to={ROUTES.guideBuying}>Księga wieczysta</Link>
            </div>
          </div>
        </div>
      </section>

      <div className="guides-container guides-layout">
        <div className="guides-main">
          <section className="guides-section">
            <div className="guide-topic-head">
              <h2>Wybierz temat</h2>
              <div>
                <button aria-label="Poprzednie"><Icon name="chevron" /></button>
                <button aria-label="Następne"><Icon name="chevron" /></button>
              </div>
            </div>
            <div className="guide-topic-grid">
              {guideTopics.map((topic) => {
                const isBuying = topic.title === 'Kupno mieszkania';
                // Klikalny kafelek to link do poradnika; pozostale nie maja jeszcze tresci.
                const content = (
                  <>
                    <span className={`guide-topic-icon ${topic.icon}`}>
                      <Icon name={topic.icon} />
                    </span>
                    <h3>{topic.title}</h3>
                    <p>{topic.text}</p>
                    <small>{topic.count}</small>
                  </>
                );

                return isBuying ? (
                  <Link className="guide-topic-card is-clickable" key={topic.title} to={ROUTES.guideBuying}>
                    {content}
                  </Link>
                ) : (
                  <article className="guide-topic-card" key={topic.title}>
                    {content}
                  </article>
                );
              })}
            </div>
          </section>

          <section className="guides-section">
            <div className="section-head">
              <h2>Polecane artykuły</h2>
              <Link to={ROUTES.guideBuying}>Zobacz wszystkie <Icon name="arrow" /></Link>
            </div>
            <div className="article-grid">
              {guideArticles.map((article) => (
                <article className="article-card" key={article.title}>
                  <div
                    className="article-image"
                    style={{ backgroundImage: `url(${article.image})`, backgroundPosition: article.position }}
                  >
                    <span>{article.category}</span>
                  </div>
                  <div className="article-body">
                    <h3>{article.title}</h3>
                    <p>{article.text}</p>
                    <div>
                      <span>{article.readTime}</span>
                      <span><Icon name="eye" /> {article.views}</span>
                    </div>
                  </div>
                </article>
              ))}
            </div>
          </section>

          <section className="related-offers">
            <h2>Powiązane oferty</h2>
            <p>Zobacz mieszkania dopasowane do Twoich zainteresowań</p>
            <div className="related-offer-grid">
              {guideRelatedOffers.map((offer) => (
                <article key={offer.title}>
                  <div>
                    <h3>{offer.title}</h3>
                    <span>{offer.count}</span>
                    <Link to={ROUTES.buy}>Zobacz oferty <Icon name="arrow" /></Link>
                  </div>
                  <div
                    className="related-offer-image"
                    style={{ backgroundImage: `url(${offer.image})`, backgroundPosition: offer.position }}
                  />
                </article>
              ))}
            </div>
          </section>
        </div>

        <aside className="guides-sidebar">
          <section className="guide-ai-card">
            <div className="ai-label">
              <Icon name="spark" />
              <span>AI Doradca Nieruchomości</span>
            </div>
            <p>Zadaj pytanie, a nasz AI doradca pomoże Ci znaleźć odpowiedź.</p>
            <div className="ai-question-box">
              <strong>Chcę kupić mieszkanie za 700 tys. w Krakowie. Mam 150 tys. wkładu własnego. Co powinienem wiedzieć?</strong>
            </div>
            <button>
              Zadaj pytanie
              <Icon name="arrow" />
            </button>
            <div className="advisor-users">
              <div className="avatar-row">
                {avatars.slice(0, 3).map((avatar) => (
                  <span key={avatar}>{avatar}</span>
                ))}
              </div>
              <small>12 489 osób skorzystało z AI doradcy</small>
            </div>
          </section>

          <section className="quick-guides-card">
            <h2>Szybkie poradniki</h2>
            <div>
              {quickGuides.map((guide) => (
                <article key={guide.title}>
                  <span>
                    <Icon name={guide.icon} />
                  </span>
                  <p>
                    <strong>{guide.title}</strong>
                    <small>{guide.text}</small>
                  </p>
                  <Icon name="chevron" />
                </article>
              ))}
            </div>
            <button>
              Zobacz wszystkie poradniki
              <Icon name="arrow" />
            </button>
          </section>
        </aside>
      </div>
    </section>
  );
}

function SellPage() {
  const navigate = useNavigate();
  return (
    <section className="sell-page" aria-label="Sprzedaj mieszkanie">
      <section className="sell-hero">
        <img className="sell-hero-photo" src={heroImage} alt="" />
        <div className="sell-hero-gradient" />
        <div className="sell-hero-inner">
          <div className="breadcrumb">
            <span>Strona główna</span>
            <Icon name="chevron" />
            <strong>Sprzedaj mieszkanie</strong>
          </div>

          <div className="sell-copy">
            <h1>
              Sprzedaj mieszkanie
              <span>szybko i bez prowizji.</span>
            </h1>
            <p>Dodaj ogłoszenie za darmo i znajdź kupca bez pośredników.</p>

            <div className="sell-benefit-row">
              <span><Icon name="check" /> 0 zł prowizji</span>
              <span><Icon name="shield" /> Bez ukrytych opłat</span>
              <span><Icon name="group" /> Tysiące kupujących</span>
              <span><Icon name="spark" /> Pełne wsparcie</span>
            </div>

            <Link className="sell-primary" to={ROUTES.add}>Dodaj ogłoszenie za darmo</Link>
            <small>Zajmie Ci to tylko 2 minuty</small>
          </div>
        </div>
      </section>

      <div className="sell-container">
        <section className="sell-section">
          <h2>Jak to działa?</h2>
          <div className="sell-steps">
            {sellSteps.map((step, index) => (
              <article className="sell-step" key={step.title}>
                <span>
                  <Icon name={step.icon} />
                </span>
                <strong>{index + 1}. {step.title}</strong>
                <p>{step.text}</p>
              </article>
            ))}
          </div>
        </section>

        <section className="sell-card-grid">
          <article className="sell-info-card">
            <h2>Dlaczego warto sprzedawać u nas?</h2>
            <div className="sell-reasons">
              {sellReasons.map((reason) => (
                <div key={reason.title}>
                  <span>
                    <Icon name={reason.icon} />
                  </span>
                  <p>
                    <strong>{reason.title}</strong>
                    <small>{reason.text}</small>
                  </p>
                </div>
              ))}
            </div>
          </article>

          <article className="sell-valuation-card">
            <h2>Sprawdź wartość swojego mieszkania</h2>
            <p>Bezpłatna wycena online w 30 sekund</p>
            <div className="valuation-input">Podaj adres mieszkania</div>
            <Link to={ROUTES.valuation}>Wyceń mieszkanie</Link>
            <div className="valuation-result">
              <span>Przykładowa wycena</span>
              <small>Kraków, Zabłocie</small>
              <strong>742 000 zł</strong>
              <em>Zakres: 720 000 - 765 000 zł</em>
              <div className="mini-building" aria-hidden="true">
                <span />
                <span />
              </div>
            </div>
          </article>

          <article className="sell-channels-card">
            <h2>Twoje ogłoszenie dotrze do:</h2>
            <div className="sell-channel-list">
              {sellChannels.map((channel) => (
                <div key={channel.title}>
                  <span>
                    <Icon name={channel.icon} />
                  </span>
                  <p>
                    <strong>{channel.title}</strong>
                    <small>{channel.text}</small>
                  </p>
                </div>
              ))}
            </div>
          </article>
        </section>

        <section className="sell-feature-strip">
          <h2>Co wyróżnia Twoje ogłoszenie?</h2>
          <div>
            {sellFeatures.map((feature) => (
              <article key={feature.title}>
                <span>
                  <Icon name={feature.icon} />
                </span>
                <p>
                  <strong>{feature.title}</strong>
                  <small>{feature.text}</small>
                </p>
              </article>
            ))}
          </div>
        </section>

        <section className="sell-section">
          <h2>Zobacz opinie naszych użytkowników</h2>
          <div className="testimonial-grid">
            {sellTestimonials.map((testimonial, index) => (
              <article className="testimonial-card" key={testimonial.name}>
                <div className="testimonial-head">
                  <span>{testimonial.name.charAt(0)}</span>
                  <p>
                    <strong>{testimonial.name}</strong>
                    <small>{testimonial.result}</small>
                  </p>
                </div>
                <p>{testimonial.text}</p>
                <div className="stars" aria-label="Ocena 5 na 5">
                  {Array.from({ length: 5 }).map((_, starIndex) => (
                    <Icon name="star" key={`${testimonial.name}-${starIndex}`} />
                  ))}
                  <span>5.0</span>
                </div>
                {index === 0 && <button className="testimonial-nav prev">‹</button>}
                {index === 2 && <button className="testimonial-nav next">›</button>}
              </article>
            ))}
          </div>
          <div className="testimonial-dots">
            <span />
            <span />
          </div>
        </section>

        <section className="sell-bottom-cta">
          <div>
            <strong>Gotowy na szybką i bezpieczną sprzedaż?</strong>
            <span>Dołącz do tysięcy osób, które już sprzedały z nami swoje mieszkanie.</span>
          </div>
          <Link to={ROUTES.add}>Dodaj ogłoszenie za darmo</Link>
          <small>Zajmie Ci to tylko 2 minuty</small>
        </section>
      </div>
    </section>
  );
}

type ValuationForm = {
  location: string;
  market: '' | 'Wtórny' | 'Pierwotny';
  propertyType: '' | 'Mieszkanie' | 'Dom' | 'Lokal użytkowy';
  area: string;
  plotArea: string;
  rooms: string;
  floor: string;
  buildingFloors: string;
  yearBuilt: string;
  buildingType: string;
  buildingMaterial: string;
  commercialPurpose: string;
  streetEntrance: boolean;
  displayWindow: boolean;
  kitchen: string;
  bathrooms: string;
  balcony: boolean;
  balconyArea: string;
  orientation: string;
  cellar: boolean;
  storage: boolean;
  parking: boolean;
  garage: boolean;
  rent: string;
  heating: string;
  hotWater: string;
  elevator: boolean;
  condition: string;
  ownership: string;
  landRegister: boolean;
  amenities: string[];
};

type ValuationResult = {
  ready: boolean;
  minValue: number;
  maxValue: number;
  averageValue: number;
  minPerMeter: number;
  maxPerMeter: number;
  averagePerMeter: number;
  confidence: number;
  validUntil: string;
  details: { icon: string; label: string; value: string }[];
  mainFactors: string[];
};

const INITIAL_VALUATION_FORM: ValuationForm = {
  location: '',
  market: '',
  propertyType: '',
  area: '',
  plotArea: '',
  rooms: '',
  floor: '',
  buildingFloors: '',
  yearBuilt: '',
  buildingType: '',
  buildingMaterial: '',
  commercialPurpose: '',
  streetEntrance: false,
  displayWindow: false,
  kitchen: '',
  bathrooms: '',
  balcony: false,
  balconyArea: '',
  orientation: '',
  cellar: false,
  storage: false,
  parking: false,
  garage: false,
  rent: '',
  heating: '',
  hotWater: '',
  elevator: false,
  condition: '',
  ownership: '',
  landRegister: false,
  amenities: [],
};

const VALUATION_STEPS = ['Lokalizacja', 'Szczegóły nieruchomości', 'Stan i udogodnienia', 'Wynik wyceny'];

const VALUATION_APARTMENT_BUILDING_TYPES = ['Blok', 'Kamienica', 'Apartamentowiec', 'Dom wielorodzinny'];
const VALUATION_HOUSE_BUILDING_TYPES = ['Wolnostojący', 'Bliźniak', 'Szeregowy', 'Segment'];
const VALUATION_COMMERCIAL_BUILDING_TYPES = ['Lokal handlowy', 'Biuro', 'Usługi', 'Gastronomia', 'Magazyn'];
const VALUATION_COMMERCIAL_PURPOSES = ['Handel', 'Biuro', 'Usługi', 'Gastronomia', 'Medyczny', 'Magazyn'];

type ValuationMarketReference = {
  label: string;
  basePerMeter: number;
  precision: 'city' | 'region' | 'national';
  locationType: string;
};

const VALUATION_2026_SOURCE_NOTE = 'model 2026: ceny transakcyjne SonarHome 2026-06-01 + korekty regionalne';

const VALUATION_PRICE_PER_SQM_BY_CITY: Record<string, number> = {
  Warszawa: 15700,
  Kraków: 14600,
  Gdańsk: 13550,
  Gdynia: 11900,
  Wrocław: 11750,
  Poznań: 10800,
  Józefów: 11600,
  'Grodzisk Mazowiecki': 10600,
  Rzeszów: 10200,
  Lublin: 9300,
  Katowice: 9200,
  Szczecin: 9100,
  Białystok: 9050,
  Olsztyn: 9000,
  Toruń: 8200,
  Opole: 8200,
  Bydgoszcz: 7950,
  'Bielsko-Biała': 7950,
  Kielce: 7900,
  'Zielona Góra': 7600,
  Łódź: 7550,
  Radom: 7000,
  Elbląg: 6950,
  Gliwice: 6900,
  Iława: 6850,
  'Gorzów Wielkopolski': 6800,
  'Jelenia Góra': 6600,
  Częstochowa: 6100,
  'Dąbrowa Górnicza': 5900,
  Chorzów: 5650,
  Będzin: 5300,
  Czeladź: 5000,
  Bytom: 4450,
  Sopot: 16300,
  Piaseczno: 11800,
  Pruszków: 11200,
  Marki: 10800,
  Ząbki: 10600,
  Otwock: 9800,
  Wieliczka: 10400,
  Zakopane: 13500,
  Kołobrzeg: 11800,
  Świnoujście: 12100,
  Puławy: 7400,
  Ostrołęka: 7000,
};

const VALUATION_PRICE_PER_SQM_BY_REGION: Record<string, number> = {
  mazowieckie: 8200,
  małopolskie: 8300,
  pomorskie: 8600,
  dolnośląskie: 7900,
  wielkopolskie: 7400,
  łódzkie: 6500,
  śląskie: 6700,
  podkarpackie: 6600,
  lubelskie: 6300,
  zachodniopomorskie: 7000,
  'kujawsko-pomorskie': 6100,
  podlaskie: 6200,
  'warmińsko-mazurskie': 6500,
  świętokrzyskie: 5900,
  opolskie: 6100,
  lubuskie: 6100,
};

const VALUATION_NATIONAL_BASE_PER_SQM = 6900;

const VALUATION_DISTRICT_MULTIPLIERS: { keyword: string; multiplier: number }[] = [
  { keyword: 'śródmieście', multiplier: 1.18 },
  { keyword: 'stare miasto', multiplier: 1.16 },
  { keyword: 'mokotów', multiplier: 1.13 },
  { keyword: 'żoliborz', multiplier: 1.12 },
  { keyword: 'wola', multiplier: 1.1 },
  { keyword: 'wilanów', multiplier: 1.1 },
  { keyword: 'ursynów', multiplier: 1.05 },
  { keyword: 'zabłocie', multiplier: 1.08 },
  { keyword: 'podgórze', multiplier: 1.05 },
  { keyword: 'kazimierz', multiplier: 1.12 },
  { keyword: 'krowodrza', multiplier: 1.04 },
  { keyword: 'oliwa', multiplier: 1.09 },
  { keyword: 'wrzeszcz', multiplier: 1.06 },
  { keyword: 'przymorze', multiplier: 1.04 },
  { keyword: 'krzyki', multiplier: 1.05 },
  { keyword: 'nadodrze', multiplier: 1.03 },
  { keyword: 'jeżyce', multiplier: 1.04 },
  { keyword: 'łazarz', multiplier: 1.02 },
  { keyword: 'bemowo', multiplier: 1.02 },
  { keyword: 'praga', multiplier: 0.98 },
  { keyword: 'nowa huta', multiplier: 0.92 },
  { keyword: 'bielany', multiplier: 0.99 },
  { keyword: 'targówek', multiplier: 0.94 },
];

const VALUATION_CONDITION_OPTIONS = [
  { icon: 'paint', title: 'Do remontu', text: 'Nieruchomość wymaga generalnego remontu.', multiplier: 0.88 },
  { icon: 'hammer', title: 'Do odświeżenia', text: 'Nieruchomość wymaga odświeżenia lub drobnych napraw.', multiplier: 0.95 },
  { icon: 'check', title: 'Dobry', text: 'Nieruchomość w dobrym stanie, gotowa do użytkowania.', multiplier: 1 },
  { icon: 'star', title: 'Bardzo dobry', text: 'Nieruchomość w bardzo dobrym stanie, bez dodatkowych nakładów.', multiplier: 1.06 },
  { icon: 'spark', title: 'Idealny', text: 'Nieruchomość jak nowa, wykończona w wysokim standardzie.', multiplier: 1.1 },
];

const VALUATION_AMENITY_OPTIONS = [
  { icon: 'sun', title: 'Balkon / loggia', impact: 0.018 },
  { icon: 'house', title: 'Taras', impact: 0.025 },
  { icon: 'spark', title: 'Ogródek', impact: 0.032 },
  { icon: 'building', title: 'Winda', impact: 0.025 },
  { icon: 'check', title: 'Miejsce parkingowe', impact: 0.03 },
  { icon: 'car', title: 'Garaż', impact: 0.04 },
  { icon: 'building', title: 'Komórka lokatorska', impact: 0.01 },
  { icon: 'list', title: 'Piwnica', impact: 0.008 },
  { icon: 'snow', title: 'Klimatyzacja', impact: 0.014 },
  { icon: 'flame', title: 'Ogrzewanie podłogowe', impact: 0.014 },
  { icon: 'screen', title: 'Rolety zewnętrzne', impact: 0.006 },
  { icon: 'camera', title: 'Monitoring', impact: 0.006 },
  { icon: 'shield', title: 'Ochrona', impact: 0.01 },
  { icon: 'user', title: 'Recepcja', impact: 0.014 },
  { icon: 'scale', title: 'Siłownia', impact: 0.012 },
  { icon: 'spark', title: 'Plac zabaw', impact: 0.006 },
];

const VALUATION_VISIBLE_AMENITY_OPTIONS = VALUATION_AMENITY_OPTIONS.filter((option) => ![
  'Balkon / loggia',
  'Winda',
  'Miejsce parkingowe',
  'Garaż',
  'Komórka lokatorska',
  'Piwnica',
].includes(option.title));

function getValuationAmenityOptions(propertyType: ValuationForm['propertyType']) {
  if (propertyType === 'Dom') {
    return VALUATION_AMENITY_OPTIONS.filter((option) => [
      'Taras',
      'Ogródek',
      'Miejsce parkingowe',
      'Garaż',
      'Piwnica',
      'Klimatyzacja',
      'Ogrzewanie podłogowe',
      'Monitoring',
      'Rolety zewnętrzne',
    ].includes(option.title));
  }
  if (propertyType === 'Lokal użytkowy') {
    return VALUATION_AMENITY_OPTIONS.filter((option) => [
      'Klimatyzacja',
      'Monitoring',
      'Ochrona',
      'Recepcja',
      'Miejsce parkingowe',
      'Rolety zewnętrzne',
    ].includes(option.title));
  }
  return VALUATION_VISIBLE_AMENITY_OPTIONS;
}

function clampNumber(value: number, min: number, max: number): number {
  return Math.min(max, Math.max(min, value));
}

function roundToThousand(value: number): number {
  return Math.round(value / 1000) * 1000;
}

function parsePositiveNumber(value: string): number | null {
  const parsed = parseNumber(value);
  return parsed != null && parsed > 0 ? parsed : null;
}

function findValuationCity(location: string): string | null {
  const normalizedLocation = location.toLowerCase();
  return Object.keys(VALUATION_PRICE_PER_SQM_BY_CITY).find((city) => normalizedLocation.includes(city.toLowerCase())) ?? null;
}

function getLocationTypeAdjustment(location: string): { multiplier: number; label: string } {
  const normalizedLocation = location.toLowerCase();
  if (normalizedLocation.includes('dzielnica') || normalizedLocation.includes('osiedle')) {
    return { multiplier: 1, label: 'dzielnica' };
  }
  if (normalizedLocation.includes('wieś') || normalizedLocation.includes('hamlet') || normalizedLocation.includes('osada')) {
    return { multiplier: 0.72, label: 'wieś' };
  }
  if (normalizedLocation.includes('powiat')) {
    return { multiplier: 0.78, label: 'powiat' };
  }
  if (normalizedLocation.includes('miejscowość')) {
    return { multiplier: 0.82, label: 'miejscowość' };
  }
  if (normalizedLocation.includes('miasto')) {
    return { multiplier: 0.9, label: 'miasto' };
  }
  return { multiplier: 0.86, label: 'lokalizacja' };
}

function resolveValuationMarket(location: string): ValuationMarketReference | null {
  const trimmedLocation = location.trim();
  if (trimmedLocation.length < 2) {
    return null;
  }
  const city = findValuationCity(trimmedLocation);
  if (city) {
    return {
      label: city,
      basePerMeter: VALUATION_PRICE_PER_SQM_BY_CITY[city],
      precision: 'city',
      locationType: 'miasto',
    };
  }
  const normalizedLocation = trimmedLocation.toLowerCase();
  const region = Object.keys(VALUATION_PRICE_PER_SQM_BY_REGION).find((regionName) => normalizedLocation.includes(regionName));
  const locationType = getLocationTypeAdjustment(trimmedLocation);
  if (region) {
    return {
      label: `${region}, ${locationType.label}`,
      basePerMeter: Math.round(VALUATION_PRICE_PER_SQM_BY_REGION[region] * locationType.multiplier / 50) * 50,
      precision: 'region',
      locationType: locationType.label,
    };
  }
  const knownCity = POLISH_CITIES.find((cityName) => normalizedLocation.includes(cityName.toLowerCase()));
  if (knownCity) {
    return {
      label: knownCity,
      basePerMeter: Math.round(VALUATION_NATIONAL_BASE_PER_SQM * 1.04 / 50) * 50,
      precision: 'region',
      locationType: 'miasto',
    };
  }
  return {
    label: `Polska, ${locationType.label}`,
    basePerMeter: Math.round(VALUATION_NATIONAL_BASE_PER_SQM * locationType.multiplier / 50) * 50,
    precision: 'national',
    locationType: locationType.label,
  };
}

function getDistrictMultiplier(location: string): number {
  const normalizedLocation = location.toLowerCase();
  return VALUATION_DISTRICT_MULTIPLIERS.find((item) => normalizedLocation.includes(item.keyword))?.multiplier ?? 1;
}

function getAreaMultiplier(area: number): number {
  if (area < 35) {
    return 1.08;
  }
  if (area <= 55) {
    return 1.03;
  }
  if (area <= 75) {
    return 1;
  }
  if (area <= 100) {
    return 0.96;
  }
  return 0.92;
}

function getRoomsMultiplier(form: ValuationForm, area: number): number {
  const rooms = parsePositiveNumber(form.rooms) ?? 0;
  if (!rooms) {
    return 1;
  }
  const areaPerRoom = area / rooms;
  if (areaPerRoom >= 14 && areaPerRoom <= 24) {
    return 1.025;
  }
  if (areaPerRoom < 11 || areaPerRoom > 32) {
    return 0.97;
  }
  return 1;
}

function getYearMultiplier(yearBuilt: string): number {
  const year = parsePositiveNumber(yearBuilt);
  if (!year) {
    if (yearBuilt === 'Przed 1990') {
      return 0.93;
    }
    if (yearBuilt === '1990-2010') {
      return 0.98;
    }
    if (yearBuilt === 'Po 2010') {
      return 1.05;
    }
    return 1;
  }
  if (year < 1990) {
    return 0.93;
  }
  if (year <= 2010) {
    return 0.98;
  }
  if (year <= 2020) {
    return 1.04;
  }
  return 1.07;
}

function getFloorMultiplier(form: ValuationForm): number {
  if (form.propertyType === 'Dom') {
    return 1;
  }
  const floor = form.floor.toLowerCase();
  const buildingFloors = parsePositiveNumber(form.buildingFloors) ?? 0;
  if (floor === 'parter') {
    return 0.97;
  }
  if (floor.includes('poddasze')) {
    return 0.96;
  }
  const floorNumber = parsePositiveNumber(form.floor);
  if (!floorNumber) {
    return 1;
  }
  if (!form.elevator && floorNumber >= 4) {
    return 0.94;
  }
  if (floorNumber >= 1 && floorNumber <= 3) {
    return 1.015;
  }
  if (buildingFloors && floorNumber === buildingFloors && buildingFloors > 4) {
    return 0.985;
  }
  return 1;
}

function getBuildingTypeMultiplier(form: ValuationForm): number {
  if (form.propertyType === 'Dom') {
    if (form.buildingType === 'Wolnostojący') {
      return 1.06;
    }
    if (form.buildingType === 'Bliźniak') {
      return 1.01;
    }
    if (form.buildingType === 'Szeregowy' || form.buildingType === 'Segment') {
      return 0.98;
    }
    return 1;
  }
  if (form.propertyType === 'Lokal użytkowy') {
    if (form.buildingType === 'Lokal handlowy' || form.commercialPurpose === 'Handel') {
      return 1.04;
    }
    if (form.buildingType === 'Usługi' || form.commercialPurpose === 'Usługi' || form.commercialPurpose === 'Medyczny') {
      return 1.02;
    }
    if (form.buildingType === 'Magazyn' || form.commercialPurpose === 'Magazyn') {
      return 0.86;
    }
    if (form.buildingType === 'Gastronomia' || form.commercialPurpose === 'Gastronomia') {
      return 0.96;
    }
    return 1;
  }
  return form.buildingType === 'Apartamentowiec' ? 1.06 : form.buildingType === 'Kamienica' ? 1.01 : form.buildingType === 'Blok' || !form.buildingType ? 1 : 0.98;
}

function getPropertyTypeDetailMultiplier(form: ValuationForm): number {
  if (form.propertyType === 'Dom') {
    const plotArea = parsePositiveNumber(form.plotArea);
    const plotMultiplier = plotArea ? plotArea >= 1000 ? 1.06 : plotArea >= 600 ? 1.035 : plotArea >= 300 ? 1.015 : 0.985 : 1;
    const garageMultiplier = form.garage ? 1.018 : 1;
    return plotMultiplier * garageMultiplier;
  }
  if (form.propertyType === 'Lokal użytkowy') {
    const entranceMultiplier = form.streetEntrance ? 1.04 : 1;
    const displayMultiplier = form.displayWindow ? 1.035 : 1;
    const purposeMultiplier = form.commercialPurpose === 'Handel' ? 1.02 : form.commercialPurpose === 'Magazyn' ? 0.92 : 1;
    return entranceMultiplier * displayMultiplier * purposeMultiplier;
  }
  return 1;
}

function getAmenityMultiplier(form: ValuationForm): number {
  const selectedImpacts = getValuationAmenityOptions(form.propertyType)
    .filter((option) => form.amenities.includes(option.title))
    .reduce((sum, option) => sum + option.impact, 0);
  const booleanImpacts = [
    form.propertyType === 'Mieszkanie' && form.balcony ? 0.006 : 0,
    form.cellar ? 0.004 : 0,
    form.storage ? 0.006 : 0,
    form.parking ? 0.008 : 0,
    form.garage ? 0.012 : 0,
    form.propertyType !== 'Dom' && form.elevator ? 0.008 : 0,
    form.landRegister ? 0.006 : -0.025,
  ].reduce((sum, value) => sum + value, 0);
  return 1 + clampNumber(selectedImpacts + booleanImpacts, -0.04, 0.16);
}

function calculateApartmentValuation(form: ValuationForm): ValuationResult {
  const area = parsePositiveNumber(form.area);
  const marketReference = resolveValuationMarket(form.location);
  if (!area || !marketReference) {
    return {
      ready: false,
      minValue: 0,
      maxValue: 0,
      averageValue: 0,
      minPerMeter: 0,
      maxPerMeter: 0,
      averagePerMeter: 0,
      confidence: 0,
      validUntil: '',
      details: [],
      mainFactors: [],
    };
  }

  const basePerMeter = marketReference.basePerMeter;
  const conditionMultiplier = VALUATION_CONDITION_OPTIONS.find((option) => option.title === form.condition)?.multiplier ?? 1;
  const marketMultiplier = form.market === 'Pierwotny' ? 1.07 : 1;
  const propertyMultiplier = form.propertyType === 'Dom' ? 0.86 : form.propertyType === 'Lokal użytkowy' ? 0.78 : 1;
  const buildingMultiplier = getBuildingTypeMultiplier(form);
  const materialMultiplier = form.buildingMaterial === 'Cegła' ? 1.025 : form.buildingMaterial === 'Żelbet' ? 1.015 : form.buildingMaterial === 'Wielka płyta' ? 0.975 : 1;
  const kitchenMultiplier = form.propertyType !== 'Lokal użytkowy' && form.kitchen === 'Oddzielna' && area >= 50 ? 1.01 : 1;
  const orientationMultiplier = form.propertyType !== 'Lokal użytkowy' && form.orientation.includes('Połud') ? 1.012 : form.orientation === 'Północ' ? 0.988 : 1;
  const rent = parsePositiveNumber(form.rent);
  const rentMultiplier = rent && area ? clampNumber(1 - Math.max(0, rent / area - 16) * 0.002, 0.97, 1.01) : 1;

  const adjustedPerMeter = basePerMeter
    * getDistrictMultiplier(form.location)
    * marketMultiplier
    * propertyMultiplier
    * getAreaMultiplier(area)
    * getRoomsMultiplier(form, area)
    * getYearMultiplier(form.yearBuilt)
    * getFloorMultiplier(form)
    * conditionMultiplier
    * buildingMultiplier
    * materialMultiplier
    * getPropertyTypeDetailMultiplier(form)
    * kitchenMultiplier
    * orientationMultiplier
    * rentMultiplier
    * getAmenityMultiplier(form);

  const averagePerMeter = Math.round(adjustedPerMeter / 50) * 50;
  const averageValue = roundToThousand(averagePerMeter * area);
  const spread = form.location.split(',').length >= 3 ? 0.055 : 0.075;
  const minValue = roundToThousand(averageValue * (1 - spread));
  const maxValue = roundToThousand(averageValue * (1 + spread));
  const minPerMeter = Math.round(minValue / area / 50) * 50;
  const maxPerMeter = Math.round(maxValue / area / 50) * 50;
  const validUntil = new Intl.DateTimeFormat('pl-PL').format(new Date(Date.now() + 30 * 24 * 60 * 60 * 1000));
  const confidence = clampNumber(Math.round(62 + (form.location.split(',').length >= 3 ? 10 : 0) + (form.amenities.length > 4 ? 8 : 0) + (form.yearBuilt ? 6 : 0) + (form.ownership ? 4 : 0) + (form.propertyType === 'Dom' && form.plotArea ? 4 : 0) + (form.propertyType === 'Lokal użytkowy' && form.commercialPurpose ? 4 : 0)), 50, 90);
  const propertyLabel = form.propertyType || 'Nieruchomość';
  const propertyDetails = [
    { icon: 'house', label: 'Typ nieruchomości', value: propertyLabel },
    { icon: 'house', label: form.propertyType === 'Dom' ? 'Powierzchnia domu' : form.propertyType === 'Lokal użytkowy' ? 'Powierzchnia lokalu' : 'Powierzchnia', value: `${form.area.replace('.', ',')} m²` },
    ...(form.propertyType === 'Dom' ? [
      { icon: 'list', label: 'Powierzchnia działki', value: form.plotArea ? `${form.plotArea.replace('.', ',')} m²` : 'Nie podano' },
      { icon: 'building', label: 'Kondygnacje', value: form.buildingFloors || 'Nie podano' },
    ] : []),
    ...(form.propertyType === 'Lokal użytkowy' ? [
      { icon: 'building', label: 'Przeznaczenie', value: form.commercialPurpose || 'Nie podano' },
      { icon: 'building', label: 'Liczba pomieszczeń', value: form.rooms || 'Nie podano' },
      { icon: 'check', label: 'Wejście z ulicy', value: form.streetEntrance ? 'Tak' : 'Nie' },
      { icon: 'screen', label: 'Witryna', value: form.displayWindow ? 'Tak' : 'Nie' },
      { icon: 'building', label: 'Poziom lokalu', value: form.floor || 'Nie podano' },
    ] : [
      { icon: 'building', label: form.propertyType === 'Dom' ? 'Liczba pokoi' : 'Liczba pokoi', value: form.rooms || 'Nie podano' },
    ]),
    ...(form.propertyType === 'Mieszkanie' ? [
      { icon: 'building', label: 'Piętro', value: `${form.floor || 'Nie podano'} z ${form.buildingFloors || '?'}` },
      { icon: 'sun', label: 'Ekspozycja okien', value: form.orientation || 'Nie podano' },
    ] : []),
  ];

  return {
    ready: true,
    minValue,
    maxValue,
    averageValue,
    minPerMeter,
    maxPerMeter,
    averagePerMeter,
    confidence,
    validUntil,
    details: [
      { icon: 'pin', label: 'Lokalizacja', value: form.location },
      { icon: 'building', label: 'Rynek', value: form.market ? `Rynek ${form.market.toLowerCase()}` : 'Nie podano' },
      { icon: 'check', label: 'Udogodnienia', value: form.amenities.length ? form.amenities.slice(0, 5).join(', ') : 'Nie wskazano' },
      ...propertyDetails,
      { icon: 'calendar', label: 'Rok budowy', value: form.yearBuilt || 'Nie podano' },
      { icon: 'document', label: 'Forma własności', value: form.ownership || 'Nie podano' },
      { icon: 'paint', label: 'Stan techniczny', value: form.condition || 'Nie podano' },
      { icon: 'list', label: 'Księga wieczysta', value: form.landRegister ? 'Tak' : 'Nie' },
      { icon: 'money', label: form.propertyType === 'Dom' ? 'Koszty utrzymania' : form.propertyType === 'Lokal użytkowy' ? 'Czynsz / koszty' : 'Czynsz administracyjny', value: rent ? `${formatPln(rent)} / mies.` : 'Nie podano' },
    ],
    mainFactors: [
      `${marketReference.label}: baza ${formatPln(basePerMeter).replace(' zł', '')} zł/m²`,
      marketReference.precision === 'city' ? 'Dokładność: miasto' : marketReference.precision === 'region' ? 'Dokładność: region i typ lokalizacji' : 'Dokładność: średnia krajowa',
      `Lokalizacja: x${getDistrictMultiplier(form.location).toFixed(2)}`,
      `Stan: x${conditionMultiplier.toFixed(2)}`,
      `Rok, budynek i typ: x${(getYearMultiplier(form.yearBuilt) * buildingMultiplier * materialMultiplier * getPropertyTypeDetailMultiplier(form)).toFixed(2)}`,
      VALUATION_2026_SOURCE_NOTE,
    ],
  };
}

async function downloadValuationReportPdf(form: ValuationForm, valuation: ValuationResult, confidenceLabel: string) {
  if (!valuation.ready) {
    window.alert('Najpierw uzupełnij dane potrzebne do wyceny.');
    return;
  }

  const today = new Intl.DateTimeFormat('pl-PL').format(new Date());
  const reportId = `PL-${Date.now().toString().slice(-8)}`;
  const fileLocation = form.location || 'nieruchomosc';
  const details = valuation.details.filter((detail) => detail.value !== 'Nie podano' && detail.value !== 'Nie wskazano');
  const leftDetails = details.filter((_, index) => index % 2 === 0);
  const rightDetails = details.filter((_, index) => index % 2 === 1);
  const makeDetailStack = (items: ValuationResult['details']) => items.flatMap((detail) => [
    { text: detail.label, style: 'metaLabel', margin: [0, 0, 0, 2] },
    { text: detail.value, style: 'metaValue', margin: [0, 0, 0, 10] },
  ]);
  const safeLocation = fileLocation.toLowerCase().replace(/[^a-z0-9ąćęłńóśźż-]+/gi, '-').replace(/^-|-$/g, '') || 'wycena';

  type PdfMakeRuntime = {
    addFontContainer: (fontContainer: PdfMakeFontContainer) => void;
    addVirtualFileSystem: (vfs: Record<string, string>) => void;
    createPdf: (definition: unknown) => { getBlob: () => Promise<Blob> };
  };

  type PdfMakeFontContainer = {
    vfs: Record<string, { data: string; encoding?: string } | string>;
    fonts: Record<string, Record<string, string>>;
  };

  const unwrapModuleDefault = <T,>(module: T | { default: T }): T => 'default' in Object(module) ? (module as { default: T }).default : module as T;
  const downloadBlob = (blob: Blob, fileName: string) => {
    const objectUrl = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = objectUrl;
    link.download = fileName;
    link.rel = 'noopener';
    document.body.appendChild(link);
    link.click();
    link.remove();
    window.setTimeout(() => URL.revokeObjectURL(objectUrl), 1000);
  };

  let pdfMake: PdfMakeRuntime;
  try {
    const pdfMakeModule = await import('pdfmake/build/pdfmake.js') as unknown as PdfMakeRuntime | { default: PdfMakeRuntime };
    const robotoFontModule = await import('pdfmake/build/fonts/Roboto.js') as unknown as PdfMakeFontContainer | { default: PdfMakeFontContainer };
    pdfMake = unwrapModuleDefault(pdfMakeModule);
    pdfMake.addFontContainer(unwrapModuleDefault(robotoFontModule));
  } catch (error) {
    console.error('Nie udało się załadować generatora PDF', error);
    window.alert('Nie udało się przygotować raportu PDF. Odśwież stronę i spróbuj ponownie.');
    return;
  }

  const docDefinition = {
    pageSize: 'A4',
    pageMargins: [40, 36, 40, 44],
    defaultStyle: {
      font: 'Roboto',
      color: '#14223a',
      fontSize: 9,
      lineHeight: 1.25,
    },
    footer: (currentPage: number, pageCount: number) => ({
      margin: [40, 0, 40, 0],
      columns: [
        { text: 'PolskaLokalnie | raport informacyjny', color: '#8793a3', fontSize: 8 },
        { text: `strona ${currentPage}/${pageCount}`, alignment: 'right', color: '#8793a3', fontSize: 8 },
      ],
    }),
    content: [
      {
        table: {
          widths: ['*', 'auto'],
          body: [[
            {
              border: [false, false, false, false],
              fillColor: '#10a15e',
              margin: [0, 0, 0, 0],
              stack: [
                { text: 'PolskaLokalnie', style: 'brand' },
                { text: `Raport wyceny nieruchomości | ${today}`, style: 'brandMeta' },
              ],
            },
            {
              border: [false, false, false, false],
              fillColor: '#10a15e',
              alignment: 'right',
              text: `Nr raportu: ${reportId}`,
              style: 'brandMeta',
              margin: [0, 20, 0, 0],
            },
          ]],
        },
        layout: 'noBorders',
        margin: [-40, -36, -40, 22],
      },
      { text: 'Raport szacunkowej wyceny nieruchomości', style: 'title' },
      {
        text: 'Dokument przygotowany automatycznie na podstawie danych podanych w formularzu oraz modelu cenowego 2026. Raport ma charakter informacyjny i nie jest operatem szacunkowym.',
        style: 'lead',
        margin: [0, 0, 0, 18],
      },
      {
        table: {
          widths: ['*', 92],
          body: [[
            {
              border: [false, false, false, false],
              stack: [
                { text: 'Szacowana wartość rynkowa', style: 'cardLabel' },
                { text: `${formatPln(valuation.minValue)} - ${formatPln(valuation.maxValue)}`, style: 'priceRange' },
                { text: `Średnio: ${formatPln(valuation.averageValue)} | ${formatPln(valuation.averagePerMeter)} / m²`, style: 'cardText' },
                { text: `Zakres za m²: ${formatPln(valuation.minPerMeter)} - ${formatPln(valuation.maxPerMeter)}`, style: 'cardText' },
              ],
            },
            {
              border: [false, false, false, false],
              fillColor: '#10a15e',
              color: '#ffffff',
              alignment: 'center',
              margin: [0, 10, 0, 8],
              stack: [
                { text: `${valuation.confidence}%`, fontSize: 18, bold: true, margin: [0, 0, 0, 2] },
                { text: confidenceLabel, fontSize: 8, bold: true },
              ],
            },
          ]],
        },
        layout: {
          hLineColor: () => '#c7e9d8',
          vLineColor: () => '#c7e9d8',
          hLineWidth: () => 1,
          vLineWidth: () => 1,
          paddingLeft: () => 14,
          paddingRight: () => 14,
          paddingTop: () => 14,
          paddingBottom: () => 14,
        },
        fillColor: '#f4fbf7',
        margin: [0, 0, 0, 20],
      },
      { text: 'Dane nieruchomości', style: 'sectionTitle' },
      {
        columns: [
          { width: '*', stack: makeDetailStack(leftDetails) },
          { width: '*', stack: makeDetailStack(rightDetails) },
        ],
        columnGap: 28,
        margin: [0, 0, 0, 8],
      },
      { text: 'Czynniki, które wpłynęły na wynik', style: 'sectionTitle' },
      {
        ul: [
          ...valuation.mainFactors,
          `Rynek: ${form.market || 'nie podano'}, typ nieruchomości: ${form.propertyType || 'nie podano'}.`,
          'Uwzględniono stan techniczny, rok i typ budynku, piętro, metraż, układ lokalu, czynsz oraz zaznaczone udogodnienia.',
        ],
        style: 'bulletList',
        margin: [0, 0, 0, 14],
      },
      { text: 'Jak czytać wynik', style: 'sectionTitle' },
      {
        ul: [
          'Zakres minimalny i maksymalny pokazuje prawdopodobny przedział ceny ofertowo-transakcyjnej dla podanych danych. Nie jest to gwarantowana cena sprzedaży.',
          'Dokładność rośnie, gdy podasz precyzyjną lokalizację, metraż, rok budowy, stan techniczny, piętro, czynsz, formę własności i udogodnienia.',
          'Model korzysta z baz cen m² dla miast, korekt regionalnych oraz mnożników za lokalizację, standard, budynek i cechy lokalu.',
        ],
        style: 'bulletList',
        margin: [0, 0, 0, 16],
      },
      {
        table: {
          widths: ['*'],
          body: [[{
            border: [false, false, false, false],
            stack: [
              { text: 'Ważne zastrzeżenie', style: 'warningTitle' },
              {
                text: 'Raport jest automatyczną, orientacyjną wyceną przygotowaną na podstawie informacji wpisanych w formularzu. Nie stanowi operatu szacunkowego, opinii rzeczoznawcy majątkowego ani oferty handlowej. Ostateczna cena może zależeć od negocjacji, popytu, stanu prawnego, dokumentów i indywidualnych cech nieruchomości.',
                style: 'warningText',
              },
            ],
          }]],
        },
        layout: {
          hLineColor: () => '#f1d396',
          vLineColor: () => '#f1d396',
          hLineWidth: () => 1,
          vLineWidth: () => 1,
          paddingLeft: () => 12,
          paddingRight: () => 12,
          paddingTop: () => 10,
          paddingBottom: () => 10,
        },
        fillColor: '#fff8eb',
      },
    ],
    styles: {
      brand: { color: '#ffffff', fontSize: 18, bold: true, margin: [40, 20, 0, 6] },
      brandMeta: { color: '#ffffff', fontSize: 9, bold: true, margin: [40, 0, 40, 20] },
      title: { fontSize: 21, bold: true, color: '#14223a', margin: [0, 0, 0, 6] },
      lead: { fontSize: 9.5, color: '#526171' },
      cardLabel: { fontSize: 9, bold: true, color: '#526171', margin: [0, 0, 0, 8] },
      priceRange: { fontSize: 20, bold: true, color: '#0b9f5c', margin: [0, 0, 0, 10] },
      cardText: { fontSize: 9.5, bold: true, color: '#27364d', margin: [0, 0, 0, 4] },
      sectionTitle: { fontSize: 14, bold: true, color: '#14223a', margin: [0, 10, 0, 10] },
      metaLabel: { fontSize: 8, bold: true, color: '#617086' },
      metaValue: { fontSize: 9.5, bold: true, color: '#14223a' },
      bulletList: { fontSize: 9, color: '#27364d' },
      warningTitle: { fontSize: 10, bold: true, color: '#8e5b12', margin: [0, 0, 0, 5] },
      warningText: { fontSize: 8.8, color: '#5a482e' },
    },
  };

  try {
    const pdfBlob = await pdfMake.createPdf(docDefinition).getBlob();
    downloadBlob(pdfBlob, `raport-wyceny-${safeLocation}.pdf`);
  } catch (error) {
    console.error('Nie udało się wygenerować raportu PDF', error);
    window.alert('Nie udało się wygenerować raportu PDF. Sprawdź dane wyceny i spróbuj ponownie.');
  }
}

// ---- Kalkulator zdolności kredytowej -------------------------------------

type CreditMaritalStatus = 'Panna / kawaler' | 'W związku' | 'Małżeństwo' | 'Rozwiedziony/a' | 'Wdowiec/wdowa';
type CreditIncomeSource = 'Umowa o pracę' | 'Umowa zlecenie' | 'Działalność gospodarcza' | 'B2B' | 'Emerytura' | 'Renta' | 'Dochód z najmu';
type CreditSeniority = 'do 3 miesięcy' | '3-12 miesięcy' | '1-3 lata' | 'powyżej 3 lat';
type CreditRepayment = 'równe' | 'malejące';

type CreditForm = {
  age: string;
  maritalStatus: CreditMaritalStatus;
  adults: number;
  children: number;
  income: string;
  partnerIncome: string;
  incomeSource: CreditIncomeSource;
  seniority: CreditSeniority;
  loanInstallments: string;
  leasing: string;
  alimony: string;
  otherObligations: string;
  creditCardLimit: string;
  accountLimit: string;
  propertyPrice: string;
  ownContribution: string;
  loanYears: number;
  interestRate: string;
  repayment: CreditRepayment;
};

const CREDIT_MARITAL_OPTIONS: CreditMaritalStatus[] = ['Panna / kawaler', 'W związku', 'Małżeństwo', 'Rozwiedziony/a', 'Wdowiec/wdowa'];
const CREDIT_INCOME_SOURCES: CreditIncomeSource[] = ['Umowa o pracę', 'Umowa zlecenie', 'Działalność gospodarcza', 'B2B', 'Emerytura', 'Renta', 'Dochód z najmu'];
const CREDIT_SENIORITY_OPTIONS: CreditSeniority[] = ['do 3 miesięcy', '3-12 miesięcy', '1-3 lata', 'powyżej 3 lat'];

const INITIAL_CREDIT_FORM: CreditForm = {
  age: '',
  maritalStatus: 'W związku',
  adults: 1,
  children: 0,
  income: '',
  partnerIncome: '',
  incomeSource: 'Umowa o pracę',
  seniority: '1-3 lata',
  loanInstallments: '',
  leasing: '',
  alimony: '',
  otherObligations: '',
  creditCardLimit: '',
  accountLimit: '',
  propertyPrice: '',
  ownContribution: '',
  loanYears: 25,
  interestRate: '7,2',
  repayment: 'równe',
};

const LIVING_COST_FIRST_ADULT = 1500;
const LIVING_COST_NEXT_ADULT = 1000;
const LIVING_COST_CHILD = 800;
const CREDIT_MAX_DTI = 0.4;
const LIMIT_MONTHLY_CHARGE = 0.05;
const CREDIT_MIN_YEARS = 5;
const CREDIT_MAX_YEARS = 35;

function creditNum(value: string): number {
  const parsed = parseNumber(value);
  return parsed == null || parsed < 0 ? 0 : parsed;
}

// Rata annuitetowa (równa) dla danego kapitału.
function annuityInstallmentForPrincipal(principal: number, monthlyRate: number, months: number): number {
  if (principal <= 0 || months <= 0) return 0;
  if (monthlyRate <= 0) return principal / months;
  return (principal * monthlyRate) / (1 - Math.pow(1 + monthlyRate, -months));
}

// Pierwsza (najwyższa) rata malejąca dla danego kapitału.
function decreasingFirstInstallmentForPrincipal(principal: number, monthlyRate: number, months: number): number {
  if (principal <= 0 || months <= 0) return 0;
  return principal / months + principal * monthlyRate;
}

// Odwrotność: maksymalny kapitał, jaki uniesie dana rata.
function principalForInstallment(installment: number, monthlyRate: number, months: number, decreasing: boolean): number {
  if (installment <= 0 || months <= 0) return 0;
  if (decreasing) {
    return installment / (1 / months + monthlyRate);
  }
  if (monthlyRate <= 0) return installment * months;
  return (installment * (1 - Math.pow(1 + monthlyRate, -months))) / monthlyRate;
}

type CreditResult = ReturnType<typeof calculateCreditworthiness>;

function calculateCreditworthiness(form: CreditForm) {
  const income = creditNum(form.income);
  const partnerIncome = creditNum(form.partnerIncome);
  const totalIncome = income + partnerIncome;
  const adults = Math.max(1, Math.round(form.adults));
  const children = Math.max(0, Math.round(form.children));
  const livingCosts = LIVING_COST_FIRST_ADULT + Math.max(0, adults - 1) * LIVING_COST_NEXT_ADULT + children * LIVING_COST_CHILD;
  const cardCharge = creditNum(form.creditCardLimit) * LIMIT_MONTHLY_CHARGE;
  const accountCharge = creditNum(form.accountLimit) * LIMIT_MONTHLY_CHARGE;
  const declaredObligations = creditNum(form.loanInstallments) + creditNum(form.leasing) + creditNum(form.alimony) + creditNum(form.otherObligations);
  const totalObligations = declaredObligations + cardCharge + accountCharge;
  const disposable = totalIncome - livingCosts - totalObligations;
  const maxRateByDti = totalIncome * CREDIT_MAX_DTI;
  const maxInstallment = Math.max(0, Math.min(maxRateByDti, disposable));

  const years = Math.min(CREDIT_MAX_YEARS, Math.max(CREDIT_MIN_YEARS, Math.round(form.loanYears)));
  const months = years * 12;
  const monthlyRate = creditNum(form.interestRate) / 100 / 12;
  const decreasing = form.repayment === 'malejące';

  const maxLoan = principalForInstallment(maxInstallment, monthlyRate, months, decreasing);
  const ownContribution = creditNum(form.ownContribution);
  const maxPropertyPrice = maxLoan + ownContribution;
  const recommendedOwnContribution = Math.round(maxPropertyPrice * 0.2);

  const propertyPrice = creditNum(form.propertyPrice);
  const loanForPrice = Math.max(0, propertyPrice - ownContribution);
  const projectedInstallment = decreasing
    ? decreasingFirstInstallmentForPrincipal(loanForPrice, monthlyRate, months)
    : annuityInstallmentForPrincipal(loanForPrice, monthlyRate, months);
  const leftoverAfterInstallment = totalIncome - livingCosts - totalObligations - projectedInstallment;
  const dsti = totalIncome > 0 ? projectedInstallment / totalIncome : 0;
  const dti = totalIncome > 0 ? totalObligations / totalIncome : 0;

  const hasIncome = totalIncome > 0;
  const ready = hasIncome && maxInstallment > 0;
  const priceExceedsCapacity = propertyPrice > 0 && (loanForPrice > maxLoan + 1 || leftoverAfterInstallment < 0);

  let ratingKey: 'verysafe' | 'safe' | 'moderate' | 'high' | 'none' | 'empty';
  if (!hasIncome) {
    ratingKey = 'empty';
  } else if (disposable <= 0) {
    ratingKey = 'none';
  } else if (propertyPrice > 0) {
    if (priceExceedsCapacity) {
      ratingKey = 'high';
    } else if (dsti <= 0.2) {
      ratingKey = 'verysafe';
    } else if (dsti <= 0.3) {
      ratingKey = 'safe';
    } else if (dsti <= 0.4) {
      ratingKey = 'moderate';
    } else {
      ratingKey = 'high';
    }
  } else {
    const ratio = totalIncome > 0 ? maxInstallment / totalIncome : 0;
    ratingKey = ratio <= 0.2 ? 'verysafe' : ratio <= 0.3 ? 'safe' : 'moderate';
  }

  return {
    income, partnerIncome, totalIncome, adults, children, livingCosts,
    cardCharge, accountCharge, declaredObligations, totalObligations,
    disposable, maxInstallment, years, months, monthlyRate, decreasing,
    maxLoan, ownContribution, maxPropertyPrice, recommendedOwnContribution,
    propertyPrice, loanForPrice, projectedInstallment, leftoverAfterInstallment,
    dsti, dti, hasIncome, ready, priceExceedsCapacity, ratingKey,
  };
}

const CREDIT_RATING_META: Record<CreditResult['ratingKey'], { label: string; tone: string; stars: number; note: string }> = {
  verysafe: { label: 'Bardzo bezpieczny poziom', tone: 'verysafe', stars: 5, note: 'Rata i koszty życia zostawiają duży zapas w budżecie.' },
  safe: { label: 'Bezpieczny poziom', tone: 'safe', stars: 4, note: 'Obciążenie mieści się w bezpiecznych granicach.' },
  moderate: { label: 'Umiarkowane obciążenie', tone: 'moderate', stars: 3, note: 'Rata jest odczuwalna – warto zachować poduszkę finansową.' },
  high: { label: 'Wysokie obciążenie', tone: 'high', stars: 2, note: 'Rata pochłania zbyt dużą część dochodu przy tej cenie.' },
  none: { label: 'Brak zdolności', tone: 'none', stars: 0, note: 'Koszty i zobowiązania przekraczają dochód – zdolność wynosi 0 zł.' },
  empty: { label: 'Uzupełnij dane', tone: 'empty', stars: 0, note: 'Podaj miesięczny dochód netto, aby obliczyć zdolność.' },
};

function creditMaxPropertyPrice(form: CreditForm): number {
  return calculateCreditworthiness(form).maxPropertyPrice;
}

type CreditRecommendation = { icon: string; label: string; detail: string; delta: number };

// Każda rekomendacja liczona jest ponownie na danych użytkownika (żadnych kwot na sztywno).
function buildCreditRecommendations(form: CreditForm, base: number): CreditRecommendation[] {
  const recs: CreditRecommendation[] = [];
  const add = (icon: string, label: string, detail: string, next: CreditForm) => {
    const delta = Math.round(creditMaxPropertyPrice(next) - base);
    if (delta > 0) recs.push({ icon, label, detail, delta });
  };
  if (creditNum(form.creditCardLimit) > 0) {
    add('credit-card', 'Zamknij kartę kredytową', 'Rezygnacja z limitu karty odciąża miesięczny budżet.', { ...form, creditCardLimit: '0' });
  }
  if (creditNum(form.accountLimit) > 0) {
    add('scale', 'Zlikwiduj limit w koncie', 'Limit w koncie obniża zdolność, nawet jeśli go nie używasz.', { ...form, accountLimit: '0' });
  }
  if (creditNum(form.loanInstallments) > 0) {
    add('check', 'Spłać bieżące kredyty', 'Zamknięcie rat uwalnia miejsce na ratę kredytu hipotecznego.', { ...form, loanInstallments: '0' });
  }
  if (Math.round(form.loanYears) < CREDIT_MAX_YEARS) {
    add('clock', `Wydłuż okres kredytu do ${CREDIT_MAX_YEARS} lat`, 'Dłuższy okres obniża ratę i podnosi maksymalną kwotę kredytu.', { ...form, loanYears: CREDIT_MAX_YEARS });
  }
  if (creditNum(form.partnerIncome) === 0 && creditNum(form.income) > 0) {
    add('group', 'Dodaj dochód partnera', 'Drugi dochód w gospodarstwie mocno zwiększa zdolność (symulacja: partner z dochodem jak Twój).', { ...form, partnerIncome: form.income });
  }
  add('coin', 'Zwiększ wkład własny o 50 000 zł', 'Wyższy wkład własny wprost podnosi dostępną cenę nieruchomości.', { ...form, ownContribution: String(creditNum(form.ownContribution) + 50000) });
  return recs.sort((a, b) => b.delta - a.delta);
}

function formatPercent(value: number): string {
  return `${(value * 100).toLocaleString('pl-PL', { maximumFractionDigits: 1 })}%`;
}

function CreditCalculatorPage({ onSearchWithBudget }: { onSearchWithBudget: (maxPrice: number) => void }) {
  const navigate = useNavigate();
  const { user } = useAuth();
  const [form, setForm] = useState<CreditForm>(INITIAL_CREDIT_FORM);
  const [highlight, setHighlight] = useState(false);
  const resultRef = useRef<HTMLDivElement | null>(null);
  const result = calculateCreditworthiness(form);
  const rating = CREDIT_RATING_META[result.ratingKey];
  const recommendations = buildCreditRecommendations(form, result.maxPropertyPrice);
  const fmtPln = (value: number) => formatPln(Math.round(value));

  const update = <Key extends keyof CreditForm>(field: Key, value: CreditForm[Key]) => {
    setForm((current) => ({ ...current, [field]: value }));
  };
  const money = (field: keyof CreditForm) => (event: React.ChangeEvent<HTMLInputElement>) =>
    update(field, event.target.value.replace(/[^\d]/g, '') as CreditForm[typeof field]);
  const stepAdults = (delta: number) => update('adults', Math.min(12, Math.max(1, form.adults + delta)));
  const stepChildren = (delta: number) => update('children', Math.min(12, Math.max(0, form.children + delta)));
  const reset = () => { setForm(INITIAL_CREDIT_FORM); };
  const handleCalculate = () => {
    setHighlight(true);
    window.setTimeout(() => setHighlight(false), 900);
    resultRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
  };

  // Donut struktury dochodów.
  const donutRadius = 52;
  const donutCirc = 2 * Math.PI * donutRadius;
  const yourLen = result.totalIncome > 0 ? (result.income / result.totalIncome) * donutCirc : 0;
  const partnerLen = result.totalIncome > 0 ? (result.partnerIncome / result.totalIncome) * donutCirc : 0;

  const detailRows: { label: string; value: string; tone?: string }[] = [
    { label: 'Łączne dochody netto', value: fmtPln(result.totalIncome) },
    { label: 'Koszty utrzymania gospodarstwa', value: fmtPln(result.livingCosts) },
    { label: 'Łączne zobowiązania', value: fmtPln(result.totalObligations) },
    { label: 'Dochód po odliczeniach', value: fmtPln(Math.max(0, result.disposable)) },
    { label: 'Zalecana maksymalna rata', value: fmtPln(result.maxInstallment) },
    { label: 'Maksymalna kwota kredytu', value: fmtPln(result.maxLoan) },
    { label: 'Maksymalna cena nieruchomości', value: fmtPln(result.maxPropertyPrice) },
    { label: 'Przewidywana rata dla wpisanej ceny', value: fmtPln(result.projectedInstallment) },
    { label: 'Kwota po opłaceniu raty', value: fmtPln(result.leftoverAfterInstallment), tone: result.leftoverAfterInstallment < 0 ? 'negative' : 'positive' },
    { label: 'Wskaźnik DTI (zobowiązania / dochód)', value: formatPercent(result.dti) },
    { label: 'Wskaźnik DSTI (rata / dochód)', value: formatPercent(result.dsti) },
  ];

  const pageBody = (
    <>
      <div className="credit-title">
          <span className="credit-title-icon"><Icon name="house" /></span>
          <div>
            <h1>Kalkulator zdolności kredytowej</h1>
            <p>Sprawdź, jaką kwotę kredytu hipotecznego możesz uzyskać i jakie będą orientacyjne warunki. Wynik przelicza się na bieżąco przy każdej zmianie.</p>
          </div>
        </div>

        <div className="credit-layout">
          <div className="credit-form-column">
            <section className="credit-card">
              <h2><span className="credit-step-badge">1</span> Twoje dane</h2>
              <div className="credit-grid credit-grid-2">
                <label className="credit-field">
                  <span>Wiek</span>
                  <input inputMode="numeric" value={form.age} onChange={(e) => update('age', e.target.value.replace(/[^\d]/g, ''))} placeholder="np. 30" />
                </label>
                <label className="credit-field">
                  <span>Stan cywilny</span>
                  <select value={form.maritalStatus} onChange={(e) => update('maritalStatus', e.target.value as CreditMaritalStatus)}>
                    {CREDIT_MARITAL_OPTIONS.map((option) => <option key={option} value={option}>{option}</option>)}
                  </select>
                </label>
                <div className="credit-field">
                  <span>Liczba osób w gospodarstwie</span>
                  <div className="credit-stepper">
                    <button type="button" onClick={() => stepAdults(-1)} aria-label="Mniej osób">–</button>
                    <strong>{form.adults}</strong>
                    <button type="button" onClick={() => stepAdults(1)} aria-label="Więcej osób">+</button>
                  </div>
                </div>
                <div className="credit-field">
                  <span>Liczba dzieci</span>
                  <div className="credit-stepper">
                    <button type="button" onClick={() => stepChildren(-1)} aria-label="Mniej dzieci">–</button>
                    <strong>{form.children}</strong>
                    <button type="button" onClick={() => stepChildren(1)} aria-label="Więcej dzieci">+</button>
                  </div>
                </div>
              </div>
            </section>

            <section className="credit-card">
              <h2><span className="credit-step-badge">2</span> Dochody miesięczne (netto)</h2>
              <div className="credit-grid credit-grid-2">
                <label className="credit-field">
                  <span>Twój dochód</span>
                  <div className="credit-input-suffix">
                    <input inputMode="numeric" value={form.income} onChange={money('income')} placeholder="0" />
                    <em>zł</em>
                  </div>
                </label>
                <label className="credit-field">
                  <span>Dochód partnera</span>
                  <div className="credit-input-suffix">
                    <input inputMode="numeric" value={form.partnerIncome} onChange={money('partnerIncome')} placeholder="0" />
                    <em>zł</em>
                  </div>
                </label>
                <label className="credit-field">
                  <span>Źródło dochodu</span>
                  <select value={form.incomeSource} onChange={(e) => update('incomeSource', e.target.value as CreditIncomeSource)}>
                    {CREDIT_INCOME_SOURCES.map((option) => <option key={option} value={option}>{option}</option>)}
                  </select>
                </label>
                <label className="credit-field">
                  <span>Staż u obecnego pracodawcy</span>
                  <select value={form.seniority} onChange={(e) => update('seniority', e.target.value as CreditSeniority)}>
                    {CREDIT_SENIORITY_OPTIONS.map((option) => <option key={option} value={option}>{option}</option>)}
                  </select>
                </label>
              </div>
            </section>

            <section className="credit-card">
              <h2><span className="credit-step-badge">3</span> Zobowiązania miesięczne</h2>
              <div className="credit-grid credit-grid-2">
                <label className="credit-field">
                  <span>Raty kredytów i pożyczek</span>
                  <div className="credit-input-suffix"><input inputMode="numeric" value={form.loanInstallments} onChange={money('loanInstallments')} placeholder="0" /><em>zł</em></div>
                </label>
                <label className="credit-field">
                  <span>Leasing</span>
                  <div className="credit-input-suffix"><input inputMode="numeric" value={form.leasing} onChange={money('leasing')} placeholder="0" /><em>zł</em></div>
                </label>
                <label className="credit-field">
                  <span>Alimenty</span>
                  <div className="credit-input-suffix"><input inputMode="numeric" value={form.alimony} onChange={money('alimony')} placeholder="0" /><em>zł</em></div>
                </label>
                <label className="credit-field">
                  <span>Inne zobowiązania</span>
                  <div className="credit-input-suffix"><input inputMode="numeric" value={form.otherObligations} onChange={money('otherObligations')} placeholder="0" /><em>zł</em></div>
                </label>
                <label className="credit-field">
                  <span>Limit karty kredytowej</span>
                  <div className="credit-input-suffix"><input inputMode="numeric" value={form.creditCardLimit} onChange={money('creditCardLimit')} placeholder="0" /><em>zł</em></div>
                  <small>Obciążenie liczone jako 5% limitu = {fmtPln(result.cardCharge)}/mies.</small>
                </label>
                <label className="credit-field">
                  <span>Limit w koncie</span>
                  <div className="credit-input-suffix"><input inputMode="numeric" value={form.accountLimit} onChange={money('accountLimit')} placeholder="0" /><em>zł</em></div>
                  <small>Obciążenie liczone jako 5% limitu = {fmtPln(result.accountCharge)}/mies.</small>
                </label>
              </div>
            </section>

            <section className="credit-card">
              <h2><span className="credit-step-badge">4</span> Informacje o kredycie</h2>
              <div className="credit-grid credit-grid-2">
                <label className="credit-field">
                  <span>Cena nieruchomości</span>
                  <div className="credit-input-suffix"><input inputMode="numeric" value={form.propertyPrice} onChange={money('propertyPrice')} placeholder="0" /><em>zł</em></div>
                </label>
                <label className="credit-field">
                  <span>Wkład własny</span>
                  <div className="credit-input-suffix"><input inputMode="numeric" value={form.ownContribution} onChange={money('ownContribution')} placeholder="0" /><em>zł</em></div>
                  <small>{creditNum(form.propertyPrice) > 0 ? `${formatPercent(creditNum(form.ownContribution) / creditNum(form.propertyPrice))} ceny nieruchomości` : 'Podaj cenę, aby zobaczyć udział wkładu'}</small>
                </label>
                <div className="credit-field credit-field-wide">
                  <span>Okres kredytu: <strong className="credit-range-value">{result.years} lat</strong></span>
                  <input type="range" min={CREDIT_MIN_YEARS} max={CREDIT_MAX_YEARS} step={1} value={result.years} onChange={(e) => update('loanYears', Number(e.target.value))} className="credit-range" />
                  <div className="credit-range-scale"><span>{CREDIT_MIN_YEARS} lat</span><span>{CREDIT_MAX_YEARS} lat</span></div>
                </div>
                <label className="credit-field">
                  <span>Oprocentowanie</span>
                  <div className="credit-input-suffix"><input inputMode="decimal" value={form.interestRate} onChange={(e) => update('interestRate', e.target.value.replace(/[^\d.,]/g, ''))} placeholder="0" /><em>%</em></div>
                </label>
                <div className="credit-field">
                  <span>Rodzaj rat</span>
                  <div className="credit-segmented">
                    <button type="button" className={form.repayment === 'równe' ? 'active' : ''} onClick={() => update('repayment', 'równe')}>Równe</button>
                    <button type="button" className={form.repayment === 'malejące' ? 'active' : ''} onClick={() => update('repayment', 'malejące')}>Malejące</button>
                  </div>
                </div>
              </div>
            </section>

            <div className="credit-actions">
              <button type="button" className="credit-btn-primary" onClick={handleCalculate}><Icon name="calculator" /> Oblicz zdolność</button>
              <button type="button" className="credit-btn-ghost" onClick={reset}><Icon name="shuffle" /> Wyczyść dane</button>
            </div>
          </div>

          <div className="credit-result-column" ref={resultRef}>
            <section className={`credit-result-hero credit-tone-${rating.tone} ${highlight ? 'pulse' : ''}`}>
              <div className="credit-hero-top">
                <span>Twoja szacunkowa zdolność kredytowa</span>
                <span className={`credit-badge credit-badge-${rating.tone}`}>{rating.label}</span>
              </div>
              <strong>{result.hasIncome ? fmtPln(result.maxLoan) : '— zł'}</strong>
              <div className="credit-stars" aria-label={`Ocena: ${rating.stars} na 5`}>
                {[1, 2, 3, 4, 5].map((n) => <span key={n} className={n <= rating.stars ? 'on' : ''}><Icon name="star" /></span>)}
              </div>
              <p>{rating.note}</p>
            </section>

            <div className="credit-tiles">
              <div className="credit-tile">
                <small>Maksymalna rata</small>
                <strong>{fmtPln(result.maxInstallment)}</strong>
                <em>{formatPercent(CREDIT_MAX_DTI)} Twoich dochodów</em>
              </div>
              <div className="credit-tile">
                <small>Maksymalny kredyt</small>
                <strong>{fmtPln(result.maxLoan)}</strong>
                <em>{result.decreasing ? 'raty malejące' : 'raty równe'} · {result.years} lat</em>
              </div>
              <div className="credit-tile">
                <small>Zalecany wkład własny</small>
                <strong>{fmtPln(result.recommendedOwnContribution)}</strong>
                <em>20% wartości nieruchomości</em>
              </div>
            </div>

            <section className="credit-card credit-details">
              <div className="credit-details-head">
                <h3>Szczegóły symulacji</h3>
                <div className="credit-donut-wrap">
                  <svg viewBox="0 0 120 120" className="credit-donut" role="img" aria-label="Struktura dochodów">
                    <circle cx="60" cy="60" r={donutRadius} className="credit-donut-track" />
                    {result.totalIncome > 0 && (
                      <circle cx="60" cy="60" r={donutRadius} className="credit-donut-you" strokeDasharray={`${yourLen} ${donutCirc - yourLen}`} strokeDashoffset={0} transform="rotate(-90 60 60)" />
                    )}
                    {result.partnerIncome > 0 && (
                      <circle cx="60" cy="60" r={donutRadius} className="credit-donut-partner" strokeDasharray={`${partnerLen} ${donutCirc - partnerLen}`} strokeDashoffset={-yourLen} transform="rotate(-90 60 60)" />
                    )}
                    <text x="60" y="55" className="credit-donut-value">{Math.round(result.totalIncome / 1000)} tys.</text>
                    <text x="60" y="72" className="credit-donut-label">łącznie</text>
                  </svg>
                  <ul className="credit-donut-legend">
                    <li><i className="credit-dot-you" /> Twój dochód <b>{fmtPln(result.income)}</b></li>
                    <li><i className="credit-dot-partner" /> Dochód partnera <b>{fmtPln(result.partnerIncome)}</b></li>
                  </ul>
                </div>
              </div>
              <dl className="credit-detail-rows">
                {detailRows.map((row) => (
                  <div key={row.label} className={`credit-detail-row ${row.tone ? `credit-detail-${row.tone}` : ''}`}>
                    <dt>{row.label}</dt>
                    <dd>{row.value}</dd>
                  </div>
                ))}
              </dl>
            </section>

            <section className="credit-cta-band">
              <div>
                <strong>Znajdź nieruchomości w zasięgu Twojej zdolności</strong>
                <span>Pokażemy oferty do {fmtPln(result.maxPropertyPrice)} (kredyt + wkład własny).</span>
              </div>
              <button type="button" disabled={result.maxPropertyPrice <= 0} onClick={() => onSearchWithBudget(result.maxPropertyPrice)}>
                Zobacz oferty <Icon name="arrow" />
              </button>
            </section>

            {recommendations.length > 0 && (
              <section className="credit-card credit-recos">
                <h3><Icon name="spark" /> Jak zwiększyć zdolność?</h3>
                <div className="credit-reco-list">
                  {recommendations.map((reco) => (
                    <div key={reco.label} className="credit-reco">
                      <span className="credit-reco-icon"><Icon name={reco.icon} /></span>
                      <div className="credit-reco-body">
                        <strong>{reco.label}</strong>
                        <small>{reco.detail}</small>
                      </div>
                      <b className="credit-reco-delta">+{formatPln(reco.delta)}</b>
                    </div>
                  ))}
                </div>
              </section>
            )}

            <p className="credit-disclaimer">
              <Icon name="shield" /> Wynik ma charakter orientacyjny i nie stanowi oferty ani decyzji banku. Rzeczywista zdolność zależy od zasad banku, historii kredytowej i indywidualnej sytuacji klienta. Nie zapisujemy Twoich danych finansowych bez Twojej zgody.
            </p>
          </div>
        </div>
    </>
  );

  if (user) {
    return (
      <section className="account-page" aria-label="Kalkulator zdolności kredytowej">
        <div className="account-layout">
          <AccountSidebar />
          <div className="account-main credit-account-main">
            {pageBody}
          </div>
        </div>
      </section>
    );
  }

  return (
    <section className="credit-page" aria-label="Kalkulator zdolności kredytowej">
      <div className="credit-shell">
        <div className="breadcrumb credit-breadcrumb">
          <Link to={ROUTES.guideBuying}>Poradnik kupna</Link>
          <Icon name="chevron" />
          <Link className="active" to={ROUTES.creditCalculator}>Kalkulator zdolności kredytowej</Link>
        </div>
        {pageBody}
      </div>
    </section>
  );
}

function ValuationPage() {
  const navigate = useNavigate();
  const [step, setStep] = useState(0);
  const [valuationForm, setValuationForm] = useState<ValuationForm>(INITIAL_VALUATION_FORM);
  const [valuationLocationSuggestOpen, setValuationLocationSuggestOpen] = useState(false);
  const [valuationLocationSuggestions, setValuationLocationSuggestions] = useState<PlaceSuggestion[]>([]);
  const [valuationLocationSuggestLoading, setValuationLocationSuggestLoading] = useState(false);
  const valuation = calculateApartmentValuation(valuationForm);
  const valuationMarketReference = resolveValuationMarket(valuationForm.location);
  const isValuationDetailsStep = step === 1;
  const isValuationConditionStep = step === 2;
  const isValuationResultStep = step === 3;
  const isHouseValuation = valuationForm.propertyType === 'Dom';
  const isCommercialValuation = valuationForm.propertyType === 'Lokal użytkowy';
  const valuationPropertyLabel = valuationForm.propertyType || 'nieruchomości';
  const valuationBuildingTypeOptions = isHouseValuation ? VALUATION_HOUSE_BUILDING_TYPES : isCommercialValuation ? VALUATION_COMMERCIAL_BUILDING_TYPES : VALUATION_APARTMENT_BUILDING_TYPES;
  const visibleValuationAmenityOptions = getValuationAmenityOptions(valuationForm.propertyType);
  const nextStepLabel = isValuationConditionStep ? 'Dalej: Wynik wyceny' : isValuationDetailsStep ? 'Dalej: Stan i udogodnienia' : 'Dalej: Szczegóły nieruchomości';
  const isLocationComplete = Boolean(valuationMarketReference && valuationForm.market && valuationForm.propertyType);
  const isDetailsComplete = Boolean(
    parsePositiveNumber(valuationForm.area)
    && valuationForm.rooms
    && (isHouseValuation || valuationForm.floor)
    && (isCommercialValuation || valuationForm.buildingFloors)
    && (valuationForm.yearBuilt || valuationForm.buildingType)
  );
  const isConditionComplete = Boolean(valuationForm.condition || valuationForm.amenities.length > 0 || valuationForm.ownership);
  const isResultComplete = Boolean(valuation.ready && isValuationResultStep);
  const valuationCompletionSteps = [
    { label: 'Lokalizacja', done: isLocationComplete },
    { label: 'Szczegóły nieruchomości', done: isDetailsComplete },
    { label: 'Stan i udogodnienia', done: isConditionComplete },
    { label: 'Wynik wyceny', done: isResultComplete },
  ];
  const progressValue = Math.round((valuationCompletionSteps.filter((item) => item.done).length / valuationCompletionSteps.length) * 100);
  const valuationConfidenceLabel = valuation.ready
    ? valuation.confidence >= 80 ? 'Wysoka dokładność' : valuation.confidence >= 65 ? 'Dobra dokładność' : 'Orientacyjna wycena'
    : 'Czeka na dane';
  const visibleValuationDrivers = isValuationDetailsStep || isValuationConditionStep
    ? [
      { icon: 'house', title: 'Poznaj wartość', text: 'Sprawdź realną wartość swojej nieruchomości.' },
      { icon: 'money', title: 'Lepsza sprzedaż', text: 'Ustal odpowiednią cenę i sprzedaj szybciej.' },
      { icon: 'shield', title: 'Bez zobowiązań', text: 'Wycena jest darmowa i niezobowiązująca.' },
    ]
    : [];
  const valuationDrivers = [
    { icon: 'pin', title: 'Lokalizacja', text: 'Jedna z najważniejszych cech wpływających na wartość.' },
    { icon: 'house', title: 'Powierzchnia i układ', text: 'Metraż, układ oraz funkcja nieruchomości.' },
    { icon: 'hammer', title: 'Stan techniczny', text: 'Standard wykończenia i stan techniczny nieruchomości.' },
    { icon: 'building', title: 'Udogodnienia', text: 'Balkon, winda, miejsce parkingowe i inne udogodnienia.' },
    { icon: 'trend', title: 'Sytuacja rynkowa', text: 'Aktualne trendy i ceny podobnych nieruchomości.' },
  ];
  const valuationBenefits = [
    { icon: 'house', title: 'Poznaj wartość', text: 'Sprawdź realną wartość swojej nieruchomości' },
    { icon: 'money', title: 'Lepsza sprzedaż', text: 'Ustal odpowiednią cenę i sprzedaj szybciej' },
    { icon: 'shield', title: 'Bez zobowiązań', text: 'Wycena jest darmowa i niezobowiązująca' },
  ];
  const valuationNextActions = [
    { icon: 'house', label: 'Dodaj ogłoszenie i znajdź kupującego' },
    { icon: 'check', label: 'Uzupełnij dane i zwiększ dokładność' },
    { icon: 'chart', label: 'Obserwuj zmiany cen w Twojej okolicy' },
    { icon: 'target', label: 'Powtórz wycenę' },
  ];
  const valuationMissingItems = [
    !valuationMarketReference
      ? { icon: 'pin', title: 'Podaj lokalizację', text: 'Wpisz miasto, dzielnicę, miejscowość albo wieś i wybierz podpowiedź z map.' }
      : null,
    !parsePositiveNumber(valuationForm.area)
      ? { icon: 'house', title: `Podaj metraż ${valuationPropertyLabel}`, text: 'Wycena startuje dopiero po wpisaniu powierzchni w m².' }
      : null,
    !valuationForm.rooms
      ? { icon: 'building', title: isCommercialValuation ? 'Dodaj liczbę pomieszczeń' : 'Dodaj liczbę pokoi', text: 'To poprawi ocenę funkcjonalności układu nieruchomości.' }
      : null,
    !valuationForm.yearBuilt && !valuationForm.buildingType
      ? { icon: 'calendar', title: 'Uzupełnij budownictwo', text: 'Rok budowy, typ budynku i technologia mocno wpływają na wynik.' }
      : null,
    !valuationForm.condition
      ? { icon: 'hammer', title: 'Wskaż stan techniczny', text: 'Standard wykończenia pomaga zawęzić przedział ceny.' }
      : null,
  ].filter((item): item is { icon: string; title: string; text: string } => item != null);
  const valuationLocationQuery = valuationForm.location.trim();
  const localValuationLocationSuggestions: PlaceSuggestion[] = (valuationLocationQuery.length >= 2
    ? POLISH_CITIES
      .filter((city) => city.toLowerCase().startsWith(valuationLocationQuery.toLowerCase()))
      .concat(POLISH_CITIES.filter((city) => !city.toLowerCase().startsWith(valuationLocationQuery.toLowerCase()) && city.toLowerCase().includes(valuationLocationQuery.toLowerCase())))
      .slice(0, 8)
    : POPULAR_CITIES).map((city, index) => ({ id: -index - 1, label: city, sublabel: 'miasto', lat: 0, lng: 0 }));
  const shownValuationLocationSuggestions = valuationLocationSuggestions.length > 0 ? valuationLocationSuggestions : localValuationLocationSuggestions;

  useEffect(() => {
    const query = valuationForm.location.trim();
    if (query.length < 2) {
      setValuationLocationSuggestions([]);
      setValuationLocationSuggestLoading(false);
      return;
    }
    const controller = new AbortController();
    setValuationLocationSuggestLoading(true);
    const timer = window.setTimeout(() => {
      fetchPlaceSuggestions(query, controller.signal)
        .then((places) => setValuationLocationSuggestions(places))
        .catch((error) => {
          if ((error as Error).name !== 'AbortError') {
            setValuationLocationSuggestions([]);
          }
        })
        .finally(() => setValuationLocationSuggestLoading(false));
    }, 250);
    return () => {
      controller.abort();
      window.clearTimeout(timer);
    };
  }, [valuationForm.location]);

  const updateValuation = <Key extends keyof ValuationForm>(field: Key, value: ValuationForm[Key]) => {
    setValuationForm((current) => {
      const next = {
        ...current,
        [field]: value,
        ...(field === 'balcony' && value === false ? { balconyArea: '' } : null),
      };
      if (field === 'propertyType') {
        const nextPropertyType = value as ValuationForm['propertyType'];
        const allowedAmenities = getValuationAmenityOptions(nextPropertyType).map((option) => option.title);
        return {
          ...next,
          amenities: current.amenities.filter((item) => allowedAmenities.includes(item)),
          ...(nextPropertyType === 'Dom' ? { floor: '', elevator: false, commercialPurpose: '', streetEntrance: false, displayWindow: false } : null),
          ...(nextPropertyType === 'Lokal użytkowy' ? { plotArea: '', kitchen: '', bathrooms: '', balcony: false, balconyArea: '', orientation: '', cellar: false, storage: false, garage: false } : null),
          ...(nextPropertyType === 'Mieszkanie' ? { plotArea: '', commercialPurpose: '', streetEntrance: false, displayWindow: false } : null),
        };
      }
      return next;
    });
  };
  const selectValuationLocation = (place: PlaceSuggestion) => {
    updateValuation('location', [place.label, place.sublabel].filter(Boolean).join(', '));
    setValuationLocationSuggestOpen(false);
  };
  const toggleValuationAmenity = (title: string) => {
    setValuationForm((current) => ({
      ...current,
      amenities: current.amenities.includes(title)
        ? current.amenities.filter((item) => item !== title)
        : [...current.amenities, title],
    }));
  };
  const resetValuation = () => {
    setValuationForm(INITIAL_VALUATION_FORM);
    setStep(0);
  };
  const goNext = () => setStep((current) => Math.min(current + 1, VALUATION_STEPS.length - 1));
  const goBack = () => setStep((current) => Math.max(current - 1, 0));

  return (
    <section className="valuation-page" aria-label="Wycena nieruchomości">
      <div className="valuation-shell">
        <div className="breadcrumb valuation-breadcrumb">
          <Link to={ROUTES.account}>Panel główny</Link>
          <Icon name="chevron" />
          <Link className="active" to={ROUTES.valuation}>Wycena nieruchomości</Link>
        </div>

        <div className={`valuation-title ${isValuationResultStep ? 'result' : ''}`}>
          <h1>{isValuationResultStep ? 'Wynik wyceny' : 'Wycena nieruchomości'} {isValuationResultStep && <Icon name="check" />}</h1>
          <p>{isValuationResultStep ? 'Dziękujemy! Oto szacowana wartość Twojej nieruchomości.' : 'Sprawdź, ile może być warta Twoja nieruchomość.'}</p>
        </div>

        <div className={`valuation-progress ${isValuationResultStep ? 'complete' : ''}`} aria-label="Postęp formularza wyceny">
          {VALUATION_STEPS.map((label, index) => (
            <button type="button" className={index <= step ? 'active' : ''} key={label} onClick={() => setStep(index)}>
              <span>{index + 1}</span>
              <strong>{label}</strong>
            </button>
          ))}
        </div>

        {isValuationResultStep ? (
          <div className="valuation-result-layout">
            <div className="valuation-result-main">
              <section className="valuation-result-hero valuation-form-card">
                <div>
                  <span>Szacowana wartość rynkowa Twojej nieruchomości</span>
                  <strong>{valuation.ready ? `${formatPln(valuation.minValue)} - ${formatPln(valuation.maxValue)}` : 'Uzupełnij lokalizację i metraż'}</strong>
                  <em>{valuation.ready ? `Przedział cenowy: ${formatPln(valuation.minPerMeter)} - ${formatPln(valuation.maxPerMeter)} / m²` : 'Wynik pojawi się po podaniu miasta oraz powierzchni.'}</em>
                  <p>{valuation.ready ? 'Wycena została przygotowana na podstawie informacji, które podałeś, oraz aktualnych danych rynkowych z Twojej okolicy. Pamiętaj, że ostateczna cena może się różnić w zależności od sytuacji rynkowej i indywidualnych cech nieruchomości.' : 'Po lewej stronie widzisz, czego jeszcze brakuje. Najpierw podaj miasto lub dzielnicę oraz metraż, a dodatkowe informacje zawężą przedział wyceny.'}</p>
                </div>
                <div className="valuation-house-graphic result" aria-hidden="true">
                  <span><Icon name="house" /></span>
                  <i />
                  <i />
                </div>
              </section>

              <section className="valuation-result-details valuation-form-card">
                <h2>{valuation.ready ? 'Szczegóły wyceny' : 'Co trzeba uzupełnić, żeby wycenić mieszkanie?'}</h2>
                <div>
                  {valuation.ready ? (
                    valuation.details.map((detail) => (
                      <article key={detail.label}>
                        <span><Icon name={detail.icon} /></span>
                        <p>
                          <strong>{detail.label}</strong>
                          <small>{detail.value}</small>
                        </p>
                      </article>
                    ))
                  ) : (
                    valuationMissingItems.map((item) => (
                      <article key={item.title}>
                        <span><Icon name={item.icon} /></span>
                        <p>
                          <strong>{item.title}</strong>
                          <small>{item.text}</small>
                        </p>
                      </article>
                    ))
                  )}
                </div>

                <div className="valuation-result-actions">
                  <button type="button" className="valuation-back" onClick={() => setStep(2)}><Icon name="arrow" /> Wróć</button>
                  <button type="button" className="valuation-back" disabled={!valuation.ready} onClick={() => downloadValuationReportPdf(valuationForm, valuation, valuationConfidenceLabel)}><Icon name="document" /> Pobierz raport (PDF)</button>
                  <Link className="valuation-next" to={ROUTES.add}>Rozpocznij sprzedaż <Icon name="arrow" /></Link>
                </div>
              </section>
            </div>

            <aside className="valuation-result-sidebar">
              <section className="valuation-preview-card valuation-summary-card">
                <h2>Podsumowanie wyceny</h2>
                <p><span>Szacowana wartość minimalna</span><strong>{valuation.ready ? formatPln(valuation.minValue) : '--- zł'}</strong></p>
                <p><span>Szacowana wartość maksymalna</span><strong>{valuation.ready ? formatPln(valuation.maxValue) : '--- zł'}</strong></p>
                <p><span>Średnia wartość</span><strong>{valuation.ready ? formatPln(valuation.averageValue) : '--- zł'}</strong></p>
                <p><span>Cena za m² (min - max)</span><strong>{valuation.ready ? `${formatPln(valuation.minPerMeter)} - ${formatPln(valuation.maxPerMeter)}` : '--- zł'}</strong></p>
                <small>{valuation.ready ? `Wycena ważna do ${valuation.validUntil}` : 'Podaj dane, aby obliczyć wycenę'}</small>
              </section>

              <section className="valuation-preview-card valuation-market-card">
                <h2>Porównanie z rynkiem lokalnym</h2>
                <div className="valuation-market-status">
                  <strong>{valuationConfidenceLabel}</strong>
                  <span>{valuation.ready ? `${valuation.confidence}% danych wpływających na wycenę` : 'Podaj lokalizację i metraż, aby policzyć porównanie.'}</span>
                </div>
                <div className={`valuation-market-scale ${valuation.ready ? '' : 'empty'}`}>
                  <span style={{ width: `${valuation.ready ? valuation.confidence : 0}%` }} />
                </div>
                <p>{valuation.ready ? 'Im więcej szczegółów o lokalu, budynku i stanie prawnym, tym węższy przedział ceny.' : 'Wynik pojawi się po uzupełnieniu podstawowych danych.'}</p>
              </section>

              <section className="valuation-preview-card valuation-next-card">
                <h2>Co dalej?</h2>
                {valuationNextActions.map((action) => (
                  <button type="button" key={action.label}>
                    <span><Icon name={action.icon} /></span>
                    <strong>{action.label}</strong>
                    <Icon name="chevron" />
                  </button>
                ))}
              </section>

              <section className="valuation-preview-card valuation-info-card">
                <h2>Informacje o wycenie</h2>
                <p>Wycena ma charakter orientacyjny i nie stanowi oferty handlowej.</p>
                <button type="button">Dowiedz się więcej o metodologii wyceny <Icon name="arrow" /></button>
              </section>
            </aside>
          </div>
        ) : (
        <div className="valuation-layout">
          <div className="valuation-main">
            <section className="valuation-form-section">
              <h2>{isValuationConditionStep ? 'Stan i udogodnienia' : isValuationDetailsStep ? `Szczegóły ${valuationPropertyLabel}` : 'Gdzie znajduje się nieruchomość?'}</h2>
              <p>{isValuationConditionStep ? `Określ stan techniczny oraz udogodnienia dla typu: ${valuationPropertyLabel}.` : isValuationDetailsStep ? 'Pola w tej sekcji zmieniają się po wyborze typu nieruchomości.' : 'Podaj lokalizację swojej nieruchomości.'}</p>

              <div className="valuation-form-card">
                {isValuationConditionStep ? (
                  <>
                    <div className="valuation-section-title">Stan techniczny nieruchomości</div>
                    <div className="valuation-condition-grid">
                      {VALUATION_CONDITION_OPTIONS.map((option) => (
                        <button type="button" className={valuationForm.condition === option.title ? 'active' : ''} key={option.title} onClick={() => updateValuation('condition', option.title)}>
                          <Icon name={option.icon} />
                          <strong>{option.title}</strong>
                          <small>{option.text}</small>
                        </button>
                      ))}
                    </div>

                    <div className="valuation-section-title">Udogodnienia dla typu: {valuationPropertyLabel}</div>
                    <div className="valuation-amenity-grid">
                      {visibleValuationAmenityOptions.map((option) => (
                        <button type="button" className={valuationForm.amenities.includes(option.title) ? 'active' : ''} key={option.title} onClick={() => toggleValuationAmenity(option.title)}>
                          <Icon name={option.icon} />
                          <span>{option.title}</span>
                        </button>
                      ))}
                    </div>

                    <div className="valuation-section-title">Stan prawny</div>
                    <div className="valuation-grid two legal-grid">
                      <label className="valuation-field">
                        <span>Forma własności</span>
                        <select value={valuationForm.ownership} onChange={(event) => updateValuation('ownership', event.target.value)}>
                          <option value="">Wybierz formę własności</option>
                          <option>Pełna własność</option>
                          <option>Spółdzielcze własnościowe</option>
                          <option>Udział</option>
                        </select>
                      </label>
                      <div className="valuation-field">
                        <span>Księga wieczysta</span>
                        <div className="valuation-toggle valuation-toggle-wide">
                          <button type="button" className={valuationForm.landRegister ? 'active' : ''} onClick={() => updateValuation('landRegister', true)}>Tak</button>
                          <button type="button" className={!valuationForm.landRegister ? 'active' : ''} onClick={() => updateValuation('landRegister', false)}>Nie</button>
                        </div>
                      </div>
                    </div>
                  </>
                ) : !isValuationDetailsStep ? (
                  <>
                    <label className="valuation-field wide">
                      <span>Miasto, dzielnica lub adres</span>
                      <div className="valuation-location-field">
                      <div className="valuation-input-control">
                        <Icon name="pin" />
                        <input
                          value={valuationForm.location}
                          onChange={(event) => { updateValuation('location', event.target.value); setValuationLocationSuggestOpen(true); }}
                          onFocus={() => setValuationLocationSuggestOpen(true)}
                          onBlur={() => setTimeout(() => setValuationLocationSuggestOpen(false), 120)}
                          placeholder="Wpisz miasto, dzielnicę lub ulicę"
                        />
                      </div>
                      {valuationLocationSuggestLoading && <span className="valuation-location-spinner" aria-hidden="true" />}
                      {valuationLocationSuggestOpen && (shownValuationLocationSuggestions.length > 0 || (!valuationLocationSuggestLoading && valuationLocationQuery.length >= 2)) && (
                        <div className="valuation-location-suggest" role="listbox" aria-label="Podpowiedzi lokalizacji">
                          {shownValuationLocationSuggestions.length > 0 ? shownValuationLocationSuggestions.map((place) => (
                            <button type="button" key={`${place.id}-${place.label}-${place.sublabel}`} onMouseDown={(event) => event.preventDefault()} onClick={() => selectValuationLocation(place)}>
                              <Icon name="pin" />
                              <span>
                                <strong>{place.label}</strong>
                                <small>{place.sublabel}</small>
                              </span>
                            </button>
                          )) : <p>Brak wyników dla „{valuationForm.location.trim()}"</p>}
                        </div>
                      )}
                      </div>
                      <small>Przykłady: Kraków, Podgórze, ul. Zabłocie 15</small>
                    </label>

                    <div className="valuation-note">
                      <span><Icon name="chart" /></span>
                      <p>
                        <strong>Lokalizacja ma duży wpływ na wartość nieruchomości.</strong>
                        <small>Im dokładniejszy adres, tym dokładniejsza wycena.</small>
                      </p>
                    </div>

                    <div className="valuation-grid two">
                      <label className="valuation-field">
                        <span>Rynek</span>
                        <select value={valuationForm.market} onChange={(event) => updateValuation('market', event.target.value as ValuationForm['market'])}>
                          <option value="">Wybierz rynek</option>
                          <option>Wtórny</option>
                          <option>Pierwotny</option>
                        </select>
                      </label>
                      <label className="valuation-field">
                        <span>Typ nieruchomości</span>
                        <select value={valuationForm.propertyType} onChange={(event) => updateValuation('propertyType', event.target.value as ValuationForm['propertyType'])}>
                          <option value="">Wybierz typ</option>
                          <option>Mieszkanie</option>
                          <option>Dom</option>
                          <option>Lokal użytkowy</option>
                        </select>
                      </label>
                    </div>
                  </>
                ) : (
                  <>
                    <div className="valuation-section-title">Informacje podstawowe</div>
                    <div className="valuation-grid four valuation-details-grid">
                      <label className="valuation-field"><span>{isHouseValuation ? 'Powierzchnia domu' : isCommercialValuation ? 'Powierzchnia lokalu' : 'Powierzchnia mieszkania'}</span><div className="valuation-input-control suffix"><input value={valuationForm.area} onChange={(event) => updateValuation('area', event.target.value)} placeholder={isHouseValuation ? 'Np. 140' : isCommercialValuation ? 'Np. 85' : 'Np. 52'} /><em>m²</em></div></label>
                      <label className="valuation-field"><span>{isCommercialValuation ? 'Liczba pomieszczeń' : 'Liczba pokoi'}</span><select value={valuationForm.rooms} onChange={(event) => updateValuation('rooms', event.target.value)}><option value="">Wybierz</option><option>{isCommercialValuation ? '1 pomieszczenie' : '1 pokój'}</option><option>{isCommercialValuation ? '2 pomieszczenia' : '2 pokoje'}</option><option>{isCommercialValuation ? '3 pomieszczenia' : '3 pokoje'}</option><option>{isCommercialValuation ? '4 pomieszczenia' : '4 pokoje'}</option><option>{isCommercialValuation ? '5+ pomieszczeń' : '5+ pokoi'}</option></select></label>
                      {!isHouseValuation && <label className="valuation-field"><span>{isCommercialValuation ? 'Poziom lokalu' : 'Piętro'}</span><select value={valuationForm.floor} onChange={(event) => updateValuation('floor', event.target.value)}><option value="">Wybierz</option><option>Parter</option><option>1</option><option>2</option><option>3</option><option>4</option><option>5+</option><option>Poddasze</option></select></label>}
                      {!isCommercialValuation && <label className="valuation-field"><span>{isHouseValuation ? 'Liczba kondygnacji' : 'Liczba pięter w budynku'}</span><select value={valuationForm.buildingFloors} onChange={(event) => updateValuation('buildingFloors', event.target.value)}><option value="">Wybierz</option><option>1</option><option>2</option><option>3</option><option>4</option><option>5+</option></select></label>}
                      {isHouseValuation && <label className="valuation-field"><span>Powierzchnia działki</span><div className="valuation-input-control suffix"><input value={valuationForm.plotArea} onChange={(event) => updateValuation('plotArea', event.target.value)} placeholder="Np. 650" /><em>m²</em></div></label>}
                      <label className="valuation-field"><span>Rok budowy</span><input value={valuationForm.yearBuilt} onChange={(event) => updateValuation('yearBuilt', event.target.value)} placeholder="Np. 2015" /></label>
                      <label className="valuation-field"><span>{isHouseValuation ? 'Typ zabudowy' : isCommercialValuation ? 'Typ lokalu' : 'Typ budynku'}</span><select value={valuationForm.buildingType} onChange={(event) => updateValuation('buildingType', event.target.value)}><option value="">Wybierz</option>{valuationBuildingTypeOptions.map((option) => <option key={option}>{option}</option>)}</select></label>
                      <label className="valuation-field"><span>Technologia budowy</span><select value={valuationForm.buildingMaterial} onChange={(event) => updateValuation('buildingMaterial', event.target.value)}><option value="">Wybierz</option><option>Wielka płyta</option><option>Cegła</option><option>Żelbet</option><option>Silikat</option></select></label>
                    </div>

                    {isCommercialValuation ? (
                      <>
                        <div className="valuation-section-title">Funkcja lokalu</div>
                        <div className="valuation-grid four valuation-details-grid compact-controls">
                          <label className="valuation-field"><span>Przeznaczenie</span><select value={valuationForm.commercialPurpose} onChange={(event) => updateValuation('commercialPurpose', event.target.value)}><option value="">Wybierz</option>{VALUATION_COMMERCIAL_PURPOSES.map((option) => <option key={option}>{option}</option>)}</select></label>
                          <div className="valuation-field"><span>Wejście z ulicy</span><div className="valuation-toggle"><button type="button" className={valuationForm.streetEntrance ? 'active' : ''} onClick={() => updateValuation('streetEntrance', true)}>Tak</button><button type="button" className={!valuationForm.streetEntrance ? 'active' : ''} onClick={() => updateValuation('streetEntrance', false)}>Nie</button></div></div>
                          <div className="valuation-field"><span>Witryna</span><div className="valuation-toggle"><button type="button" className={valuationForm.displayWindow ? 'active' : ''} onClick={() => updateValuation('displayWindow', true)}>Tak</button><button type="button" className={!valuationForm.displayWindow ? 'active' : ''} onClick={() => updateValuation('displayWindow', false)}>Nie</button></div></div>
                          <label className="valuation-field"><span>Czynsz / koszty</span><div className="valuation-input-control suffix wide-suffix"><input value={valuationForm.rent} onChange={(event) => updateValuation('rent', event.target.value)} placeholder="Np. 1800" /><em>zł / mies.</em></div></label>
                        </div>
                      </>
                    ) : (
                      <>
                        <div className="valuation-section-title">Rozkład i orientacja</div>
                        <div className="valuation-grid five valuation-details-grid">
                          <label className="valuation-field"><span>Kuchnia</span><select value={valuationForm.kitchen} onChange={(event) => updateValuation('kitchen', event.target.value)}><option value="">Wybierz</option><option>Aneks</option><option>Oddzielna</option></select></label>
                          <label className="valuation-field"><span>Łazienka</span><select value={valuationForm.bathrooms} onChange={(event) => updateValuation('bathrooms', event.target.value)}><option value="">Wybierz</option><option>1</option><option>2</option><option>3+</option></select></label>
                          {!isHouseValuation && <div className="valuation-field"><span>Balkon</span><div className="valuation-toggle"><button type="button" className={valuationForm.balcony ? 'active' : ''} onClick={() => updateValuation('balcony', true)}>Tak</button><button type="button" className={!valuationForm.balcony ? 'active' : ''} onClick={() => updateValuation('balcony', false)}>Nie</button></div></div>}
                          {!isHouseValuation && valuationForm.balcony && <label className="valuation-field"><span>Powierzchnia balkonu</span><div className="valuation-input-control suffix"><input value={valuationForm.balconyArea} onChange={(event) => updateValuation('balconyArea', event.target.value)} placeholder="Np. 5" /><em>m²</em></div></label>}
                          <label className="valuation-field"><span>Orientacja okien</span><select value={valuationForm.orientation} onChange={(event) => updateValuation('orientation', event.target.value)}><option value="">Wybierz</option><option>Południowy zachód</option><option>Południe</option><option>Wschód</option><option>Zachód</option><option>Północ</option></select></label>
                        </div>

                        <div className="valuation-note compact"><span><Icon name="sun" /></span><p><small>Orientacja okien ma wpływ na doświetlenie nieruchomości, co może wpływać na jej wartość.</small></p></div>
                      </>
                    )}

                    <div className="valuation-section-title">Dodatkowe pomieszczenia</div>
                    <div className="valuation-grid four valuation-details-grid compact-controls">
                      {[
                        { label: 'Piwnica', field: 'cellar' as const },
                        ...(!isCommercialValuation ? [{ label: 'Komórka lokatorska', field: 'storage' as const }] : []),
                        { label: 'Miejsce parkingowe', field: 'parking' as const },
                        ...(!isCommercialValuation ? [{ label: 'Garaż', field: 'garage' as const }] : []),
                      ].map((item) => (
                        <div className="valuation-field" key={item.label}>
                          <span>{item.label}</span>
                          <div className="valuation-toggle"><button type="button" className={valuationForm[item.field] ? 'active' : ''} onClick={() => updateValuation(item.field, true)}>Tak</button><button type="button" className={!valuationForm[item.field] ? 'active' : ''} onClick={() => updateValuation(item.field, false)}>Nie</button></div>
                        </div>
                      ))}
                    </div>

                    <div className="valuation-section-title">Informacje dodatkowe</div>
                    <div className="valuation-grid four valuation-details-grid compact-controls">
                      {!isCommercialValuation && <label className="valuation-field"><span>{isHouseValuation ? 'Koszty utrzymania' : 'Czynsz administracyjny'}</span><div className="valuation-input-control suffix wide-suffix"><input value={valuationForm.rent} onChange={(event) => updateValuation('rent', event.target.value)} placeholder="Np. 700" /><em>zł / mies.</em></div></label>}
                      <label className="valuation-field"><span>Ogrzewanie</span><select value={valuationForm.heating} onChange={(event) => updateValuation('heating', event.target.value)}><option value="">Wybierz</option><option>Miejskie</option><option>Gazowe</option><option>Elektryczne</option></select></label>
                      <label className="valuation-field"><span>Ciepła woda</span><select value={valuationForm.hotWater} onChange={(event) => updateValuation('hotWater', event.target.value)}><option value="">Wybierz</option><option>Miejska</option><option>Piec gazowy</option><option>Bojler</option></select></label>
                      {!isHouseValuation && <div className="valuation-field"><span>Winda</span><div className="valuation-toggle"><button type="button" className={valuationForm.elevator ? 'active' : ''} onClick={() => updateValuation('elevator', true)}>Tak</button><button type="button" className={!valuationForm.elevator ? 'active' : ''} onClick={() => updateValuation('elevator', false)}>Nie</button></div></div>}
                    </div>
                  </>
                )}

                <div className="valuation-actions-row">
                  <button type="button" className="valuation-back" onClick={goBack}>Wstecz</button>
                  <button type="button" className="valuation-next" onClick={goNext}>{nextStepLabel} <Icon name="arrow" /></button>
                </div>
              </div>
            </section>
          </div>

          <aside className="valuation-sidebar">
            <section className="valuation-preview-card">
              <h2>Podgląd wyceny</h2>
              <div className="valuation-estimate-box">
                <div>
                  <span>Szacowana wartość rynkowa</span>
                  {valuation.ready ? <strong className="valuation-price-range">{formatPln(valuation.minValue)} - {formatPln(valuation.maxValue)}</strong> : <strong>0</strong>}
                  <small>Przedział cenowy</small>
                  <em>{valuation.ready ? `${formatPln(valuation.minPerMeter)} - ${formatPln(valuation.maxPerMeter)} / m²` : '0 zł / m²'}</em>
                  <p>{valuation.ready ? `Średnio: ${formatPln(valuation.averageValue)} (${formatPln(valuation.averagePerMeter)} / m²).` : 'Podaj miasto oraz powierzchnię, aby zobaczyć wynik wyceny.'}</p>
                </div>
                <div className="valuation-house-graphic" aria-hidden="true">
                  <span><Icon name="house" /></span>
                  <i />
                  <i />
                </div>
              </div>
            </section>

            {!isValuationResultStep && (
              <section className="valuation-preview-card">
                <h2>Postęp wyceny</h2>
                <div className="valuation-progress-card">
                  <div>
                    <strong>Uzupełnienie formularza</strong>
                    <span>{progressValue}%</span>
                  </div>
                  <div className="valuation-progress-bar"><span style={{ width: `${progressValue}%` }} /></div>
                  <div className="valuation-check-list">
                    {valuationCompletionSteps.map((item) => (
                      <p className={item.done ? 'done' : ''} key={item.label}>
                        <Icon name="check" />
                        <span>{item.label}</span>
                      </p>
                    ))}
                  </div>
                </div>
              </section>
            )}

            {!(isValuationDetailsStep || isValuationConditionStep) && (
              <section className="valuation-preview-card">
                <h2>Co wpływa na wycenę?</h2>
                <div className="valuation-driver-list">
                  {valuationDrivers.map((driver) => (
                    <div key={driver.title}>
                      <span><Icon name={driver.icon} /></span>
                      <p>
                        <strong>{driver.title}</strong>
                        <small>{driver.text}</small>
                      </p>
                    </div>
                  ))}
                </div>
              </section>
            )}

            <section className="valuation-preview-card">
              <h2>Dlaczego warto wycenić mieszkanie?</h2>
              <div className={isValuationDetailsStep || isValuationConditionStep ? 'valuation-driver-list compact' : 'valuation-benefit-grid'}>
                {(isValuationDetailsStep || isValuationConditionStep ? visibleValuationDrivers : valuationBenefits).map((benefit) => (
                  <div key={benefit.title}>
                    <span><Icon name={benefit.icon} /></span>
                    <p>
                      <strong>{benefit.title}</strong>
                      <small>{benefit.text}</small>
                    </p>
                  </div>
                ))}
              </div>
            </section>

            {valuation.ready && (
              <section className="valuation-preview-card">
                <h2>Założenia obliczeń</h2>
                <div className="valuation-driver-list compact">
                  {valuation.mainFactors.map((factor) => (
                    <div key={factor}>
                      <span><Icon name="chart" /></span>
                      <p><small>{factor}</small></p>
                    </div>
                  ))}
                </div>
              </section>
            )}
          </aside>
        </div>
        )}

        {!isValuationResultStep && <div className="valuation-trust-row">
          <span><Icon name="shield" /></span>
          <p><strong>Twoje dane są bezpieczne</strong><small>Nie udostępniamy Twoich danych osobom trzecim.</small></p>
          <span><Icon name="house" /></span>
          <p><strong>Darmowa wycena</strong><small>Wycena jest całkowicie darmowa i bez zobowiązań.</small></p>
          <span><Icon name="clock" /></span>
          <p><strong>Szybki wynik</strong><small>Wynik wyceny otrzymasz natychmiast po wypełnieniu formularza.</small></p>
        </div>}
      </div>
    </section>
  );
}

// Lista firm i usług pod /firmy. Kategoria pochodzi z query stringa (?kategoria=remonty),
// więc widok da się skopiować, odświeżyć i otworzyć w nowej karcie.
function CompaniesPage() {
  const [searchParams, setSearchParams] = useSearchParams();
  const categoryParam = searchParams.get('kategoria');
  const activeCategory = serviceCategories.find((item) => item.slug === categoryParam) ?? null;

  const companies = activeCategory
    ? companyDirectory.filter((company) => company.category === activeCategory.slug)
    : companyDirectory;

  // Zmiana filtra to zmiana adresu - replace, zeby nie zasmiecac historii przy przelaczaniu.
  const selectCategory = (slug: string | null) => {
    setSearchParams(slug ? { kategoria: slug } : {}, { replace: true });
  };

  return (
    <section className="services-page companies-page" aria-label="Firmy i usługi">
      <div className="services-frame companies-frame">
        <div className="services-main">
          <div className="breadcrumb">
            <Link to={ROUTES.home}>Strona główna</Link>
            <Icon name="chevron" />
            <Link to={ROUTES.services}>Firmy i usługi</Link>
            <Icon name="chevron" />
            <strong>{activeCategory ? activeCategory.title : 'Wszystkie firmy'}</strong>
          </div>

          <section className="companies-head">
            <h1>{activeCategory ? activeCategory.title : 'Wszystkie firmy i usługi'}</h1>
            <p>
              {activeCategory
                ? activeCategory.text
                : 'Sprawdzone firmy i specjaliści z całej Polski. Porównaj oferty, oceny i opinie klientów.'}
            </p>
            <span className="companies-count">
              {companies.length === 1 ? '1 firma' : `${companies.length} firm`}
              {activeCategory ? ` w kategorii „${activeCategory.title}”` : ' w bazie'}
            </span>
          </section>

          <nav className="companies-filters" aria-label="Filtr kategorii">
            <button
              type="button"
              className={activeCategory ? 'companies-chip' : 'companies-chip active'}
              onClick={() => selectCategory(null)}
            >
              Wszystkie <span>{companyDirectory.length}</span>
            </button>
            {serviceCategories.map((category) => {
              const count = companyDirectory.filter((company) => company.category === category.slug).length;
              return (
                <button
                  key={category.slug}
                  type="button"
                  className={activeCategory?.slug === category.slug ? 'companies-chip active' : 'companies-chip'}
                  onClick={() => selectCategory(category.slug)}
                >
                  <Icon name={category.icon} /> {category.title} <span>{count}</span>
                </button>
              );
            })}
          </nav>

          <section className="services-section">
            <div className="service-mini-search">
              <SearchField icon="pin" label="Lokalizacja" value="Cała Polska" />
              <SearchField icon="chevron" label="Usługa" value={activeCategory ? activeCategory.title : 'Wszystkie'} />
              <SearchField icon="chevron" label="Sortuj według" value="Polecane" />
              <button><Icon name="list" /> Filtry</button>
            </div>

            <div className="directory-list companies-list">
              {companies.map((company) => (
                <article className="directory-card" key={company.title}>
                  <div
                    className="directory-photo"
                    style={{ backgroundImage: `url(${company.image})`, backgroundPosition: company.position }}
                  />
                  <div>
                    <h3>{company.title}</h3>
                    <p><Icon name="star" /> {company.rating}</p>
                    <span>{company.text}</span>
                    <small><Icon name="pin" /> {company.city}</small>
                    <em><Icon name="check" /> Zweryfikowana firma</em>
                  </div>
                  <button>Zobacz profil</button>
                  <Icon name="heart" />
                </article>
              ))}
            </div>
          </section>

          <section className="services-section service-steps-section">
            <h2>Jak to działa?</h2>
            <div className="service-steps">
              {serviceSteps.map((step) => (
                <article className="service-step" key={step.title}>
                  <span>
                    <Icon name={step.icon} />
                  </span>
                  <div>
                    <h3>{step.title}</h3>
                    <p>{step.text}</p>
                  </div>
                </article>
              ))}
            </div>
          </section>
        </div>
      </div>
    </section>
  );
}

function ServicesPage() {
  return (
    <section className="services-page" aria-label="Firmy i usługi">
      <div className="services-frame">
        <div className="services-main">
          <div className="breadcrumb">
            <span>Strona główna</span>
            <Icon name="chevron" />
            <strong>Firmy i usługi</strong>
          </div>

          <section className="services-hero">
            <img src={heroImage} alt="" />
            <div className="services-hero-copy">
              <h1>
                Wszystkie usługi dla <span>Twojego domu</span>
              </h1>
              <p>Sprawdzone firmy i specjaliści, którzy pomogą Ci na każdym etapie zakupu, wykończenia i zamieszkania.</p>
            </div>
          </section>

          <div className="services-search">
            <SearchField icon="pin" label="" value="Jakiej usługi szukasz?" />
            <SearchField icon="chevron" label="Lokalizacja" value="Cała Polska" />
            <button className="buy-search-button">Szukaj</button>
          </div>

          <section className="services-section">
            <h2>Kategorie usług</h2>
            <div className="service-category-grid">
              {serviceCategories.map((category) => (
                <article className="service-category-card" key={category.title}>
                  <span className={`service-icon ${category.icon}`}>
                    <Icon name={category.icon} />
                  </span>
                  <h3>{category.title}</h3>
                  <p>{category.text}</p>
                  <Link to={companiesPath(category.slug)}>Zobacz firmy <Icon name="arrow" /></Link>
                </article>
              ))}
            </div>
          </section>

          <section className="services-section">
            <div className="section-head">
              <h2>Polecane firmy</h2>
              <Link to={ROUTES.companies}>Zobacz wszystkie <Icon name="arrow" /></Link>
            </div>
            <div className="recommended-company-grid">
              {serviceCompanies.map((company) => (
                <article className="recommended-company" key={company.title}>
                  <span className="recommended-badge">
                    <Icon name="check" />
                    Polecana
                  </span>
                  <div className={`company-logo ${company.icon}`}>
                    <Icon name={company.icon} />
                  </div>
                  <h3>{company.title}</h3>
                  <p><Icon name="star" /> {company.rating}</p>
                  <small>{company.text}</small>
                  <em>{company.city}</em>
                  <button>Zobacz profil</button>
                </article>
              ))}
            </div>
          </section>

          <section className="services-section service-steps-section">
            <h2>Jak to działa?</h2>
            <div className="service-steps">
              {serviceSteps.map((step) => (
                <article className="service-step" key={step.title}>
                  <span>
                    <Icon name={step.icon} />
                  </span>
                  <div>
                    <h3>{step.title}</h3>
                    <p>{step.text}</p>
                  </div>
                </article>
              ))}
            </div>
          </section>
        </div>

        <aside className="services-preview" aria-label="Podgląd firm">
          <section className="service-panel directory-panel">
            <div className="breadcrumb mini">
              <span>Strona główna</span>
              <Icon name="chevron" />
              <span>Firmy i usługi</span>
              <Icon name="chevron" />
              <strong>Remont i wykończenie</strong>
            </div>
            <h2>Remont i wykończenie</h2>
            <p>Znajdź sprawdzoną firmę remontową lub fachowca. Porównaj oferty i opinie klientów.</p>

            <div className="service-mini-search">
              <SearchField icon="chevron" label="Lokalizacja" value="Cała Polska" />
              <SearchField icon="chevron" label="Usługa" value="Wszystkie" />
              <SearchField icon="chevron" label="Sortuj według" value="Polecane" />
              <button><Icon name="list" /> Filtry</button>
            </div>

            <div className="directory-list">
              {serviceDirectory.map((company) => (
                <article className="directory-card" key={company.title}>
                  <div
                    className="directory-photo"
                    style={{ backgroundImage: `url(${company.image})`, backgroundPosition: company.position }}
                  />
                  <div>
                    <h3>{company.title}</h3>
                    <p><Icon name="star" /> {company.rating}</p>
                    <span>{company.text}</span>
                    <small><Icon name="pin" /> {company.city}</small>
                    <em><Icon name="check" /> Zweryfikowana firma</em>
                  </div>
                  <button>Zobacz profil</button>
                  <Icon name="heart" />
                </article>
              ))}
            </div>
            <button className="more-companies">Pokaż więcej firm <Icon name="chevron" /></button>
          </section>

          <section className="service-panel profile-panel">
            <div className="profile-cover" style={{ backgroundImage: `url(${heroImage})` }} />
            <div className="profile-head">
              <div className="profile-logo">
                <Icon name="hammer" />
              </div>
              <div>
                <h2>Remont Masters</h2>
                <p><Icon name="star" /> 4.8 (96 opinii)</p>
                <span>Kompleksowe remonty mieszkań i domów</span>
              </div>
              <button>Skontaktuj się</button>
            </div>
            <div className="profile-actions">
              <button><Icon name="phone" /> Zadzwoń</button>
              <button><Icon name="message" /> Napisz wiadomość</button>
              <button><Icon name="heart" /> Dodaj do ulubionych</button>
            </div>
            <div className="profile-tabs">
              <button className="active">O firmie</button>
              <button>Usługi</button>
              <button>Opinie (96)</button>
              <button>Realizacje</button>
              <button>Kontakt</button>
            </div>
            <div className="profile-about">
              <h3>O firmie</h3>
              <p>Remont Masters to zespół doświadczonych fachowców z pasją do tworzenia pięknych wnętrz. Oferujemy kompleksowe remonty mieszkań i domów.</p>
              <div className="profile-stats">
                <span><Icon name="calendar" /> 12 lat na rynku</span>
                <span><Icon name="group" /> 230+ zrealizowanych projektów</span>
                <span><Icon name="shield" /> Gwarancja na usługi</span>
                <span><Icon name="document" /> Faktura VAT</span>
              </div>
            </div>
          </section>
        </aside>
      </div>
    </section>
  );
}

function parseNumber(value: string): number | null {
  const normalized = value.replace(/\s/g, '').replace(',', '.');
  if (normalized === '') {
    return null;
  }
  const parsed = Number(normalized);
  return Number.isNaN(parsed) ? null : parsed;
}

function flatsWord(count: number): string {
  if (count === 1) {
    return 'mieszkanie';
  }
  const ones = count % 10;
  const tens = count % 100;
  if (ones >= 2 && ones <= 4 && !(tens >= 12 && tens <= 14)) {
    return 'mieszkania';
  }
  return 'mieszkań';
}

function thousands(value: number): string {
  return `${Math.round(value / 1000)} tys.`;
}

const propertyTypeOptions: PropertySearchType[] = ['Mieszkanie', 'Dom', 'Działka', 'Lokal użytkowy', 'Pokój'];

function useFilterState() {
  const [cityInput, setCityInput] = useState('');
  const [city, setCity] = useState('');
  const [propertyType, setPropertyType] = useState(propertyTypeOptions[0]);
  const [priceMin, setPriceMin] = useState('');
  const [priceMax, setPriceMax] = useState('');
  const [areaMin, setAreaMin] = useState('');
  const [areaMax, setAreaMax] = useState('');
  const [rooms, setRooms] = useState<number | null>(null);
  const [wtorny, setWtorny] = useState(true);
  const [pierwotny, setPierwotny] = useState(true);
  const [noFeeOnly, setNoFeeOnly] = useState(false);
  const [more, setMore] = useState<Record<string, string>>({});

  const pMin = parseNumber(priceMin);
  const pMax = parseNumber(priceMax);
  const aMin = parseNumber(areaMin);
  const aMax = parseNumber(areaMax);

  let priceLabel = 'Dowolna';
  if (pMin != null && pMax != null) {
    priceLabel = `${thousands(pMin)} – ${thousands(pMax)} zł`;
  } else if (pMax != null) {
    priceLabel = `do ${formatPln(pMax)}`;
  } else if (pMin != null) {
    priceLabel = `od ${formatPln(pMin)}`;
  }

  let areaLabel = 'Dowolny';
  if (aMin != null && aMax != null) {
    areaLabel = `${aMin} – ${aMax} m²`;
  } else if (aMax != null) {
    areaLabel = `do ${aMax} m²`;
  } else if (aMin != null) {
    areaLabel = `od ${aMin} m²`;
  }

  const roomsLabelValue = rooms == null ? 'Dowolna' : rooms === 5 ? '5+ pokoi' : roomsLabel(rooms);
  const roomsShort = rooms == null ? 'Dowolne' : rooms === 5 ? '5+' : String(rooms);
  const marketLabel = wtorny && pierwotny ? 'Wtórny i pierwotny' : wtorny ? 'Wtórny' : pierwotny ? 'Pierwotny' : 'Brak';

  const resetAll = () => {
    setCity('');
    setCityInput('');
    setPropertyType(propertyTypeOptions[0]);
    setPriceMin('');
    setPriceMax('');
    setAreaMin('');
    setAreaMax('');
    setRooms(null);
    setWtorny(true);
    setPierwotny(true);
    setNoFeeOnly(false);
    setMore({});
  };

  const clearCity = () => {
    setCity('');
    setCityInput('');
  };

  const chips: { label: string; onRemove: () => void }[] = [];
  if (city) {
    chips.push({ label: city, onRemove: clearCity });
  }
  if (pMin != null || pMax != null) {
    chips.push({ label: priceLabel, onRemove: () => { setPriceMin(''); setPriceMax(''); } });
  }
  if (aMin != null || aMax != null) {
    chips.push({ label: areaLabel, onRemove: () => { setAreaMin(''); setAreaMax(''); } });
  }
  if (rooms != null) {
    chips.push({ label: roomsLabelValue, onRemove: () => setRooms(null) });
  }
  if (!(wtorny && pierwotny)) {
    chips.push({ label: marketLabel, onRemove: () => { setWtorny(true); setPierwotny(true); } });
  }
  if (noFeeOnly) {
    chips.push({ label: 'Bez prowizji', onRemove: () => setNoFeeOnly(false) });
  }

  return {
    cityInput, setCityInput, city, setCity,
    propertyType, setPropertyType,
    priceMin, setPriceMin, priceMax, setPriceMax,
    areaMin, setAreaMin, areaMax, setAreaMax,
    rooms, setRooms, wtorny, setWtorny, pierwotny, setPierwotny,
    noFeeOnly, setNoFeeOnly, more, setMore,
    pMin, pMax, aMin, aMax,
    priceLabel, areaLabel, roomsLabelValue, roomsShort, marketLabel,
    resetAll, clearCity, chips,
  };
}

type FilterState = ReturnType<typeof useFilterState>;

type SavedSearchDraft = Omit<SavedSearchRecord, 'id' | 'createdAt' | 'updatedAt'>;

function sanitizeSavedSearchMore(more: Record<string, string>): Record<string, string> {
  return Object.fromEntries(
    Object.entries(more)
      .filter(([, value]) => {
        const trimmed = value.trim();
        return trimmed.length > 0 && !trimmed.toLowerCase().startsWith('dowoln');
      })
      .sort(([left], [right]) => left.localeCompare(right)),
  );
}

function savedSearchMoreSignature(more: Record<string, string>): string {
  return Object.entries(sanitizeSavedSearchMore(more))
    .map(([key, value]) => `${key}:${value}`)
    .join('|');
}

function buildSavedSearchDraft(mode: SavedSearchMode, filters: FilterState): SavedSearchDraft {
  return {
    mode,
    city: filters.city.trim(),
    propertyType: filters.propertyType,
    priceMin: filters.pMin,
    priceMax: filters.pMax,
    areaMin: filters.aMin,
    areaMax: filters.aMax,
    rooms: filters.rooms,
    wtorny: filters.wtorny,
    pierwotny: filters.pierwotny,
    noFeeOnly: filters.noFeeOnly,
    more: sanitizeSavedSearchMore(filters.more),
  };
}

function sameSavedSearchCriteria(savedSearch: SavedSearchRecord | SavedSearchDraft, draft: SavedSearchDraft): boolean {
  return (
    savedSearch.mode === draft.mode
    && savedSearch.city === draft.city
    && savedSearch.propertyType === draft.propertyType
    && savedSearch.priceMin === draft.priceMin
    && savedSearch.priceMax === draft.priceMax
    && savedSearch.areaMin === draft.areaMin
    && savedSearch.areaMax === draft.areaMax
    && savedSearch.rooms === draft.rooms
    && savedSearch.wtorny === draft.wtorny
    && savedSearch.pierwotny === draft.pierwotny
    && savedSearch.noFeeOnly === draft.noFeeOnly
    && savedSearchMoreSignature(savedSearch.more) === savedSearchMoreSignature(draft.more)
  );
}

function savedSearchPlaceLabel(savedSearch: SavedSearchRecord): string {
  return savedSearch.city || 'Cała Polska';
}

function savedSearchModeLabel(mode: SavedSearchMode): string {
  return mode === 'rent' ? 'Wynajem' : 'Kupno';
}

function savedSearchUpdatedLabel(iso: string): string {
  const date = new Date(iso);
  if (Number.isNaN(date.getTime())) {
    return 'Przed chwilą';
  }
  const now = new Date();
  const sameDay = now.toDateString() === date.toDateString();
  if (sameDay) {
    return `Dzisiaj, ${date.toLocaleTimeString('pl-PL', { hour: '2-digit', minute: '2-digit' })}`;
  }
  const yesterday = new Date(now);
  yesterday.setDate(now.getDate() - 1);
  if (yesterday.toDateString() === date.toDateString()) {
    return `Wczoraj, ${date.toLocaleTimeString('pl-PL', { hour: '2-digit', minute: '2-digit' })}`;
  }
  return date.toLocaleString('pl-PL', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' });
}

function savedSearchMetaLabel(savedSearch: SavedSearchRecord): string {
  const parts: string[] = [savedSearch.propertyType];

  if (savedSearch.rooms != null) {
    parts.push(savedSearch.rooms === 5 ? '5+ pokoi' : `${savedSearch.rooms} pokoje`);
  }

  if (savedSearch.areaMin != null || savedSearch.areaMax != null) {
    if (savedSearch.areaMin != null && savedSearch.areaMax != null) {
      parts.push(`${savedSearch.areaMin}-${savedSearch.areaMax} m²`);
    } else if (savedSearch.areaMin != null) {
      parts.push(`od ${savedSearch.areaMin} m²`);
    } else if (savedSearch.areaMax != null) {
      parts.push(`do ${savedSearch.areaMax} m²`);
    }
  }

  if (savedSearch.priceMin != null || savedSearch.priceMax != null) {
    if (savedSearch.priceMin != null && savedSearch.priceMax != null) {
      parts.push(`${formatPln(savedSearch.priceMin)}-${formatPln(savedSearch.priceMax)}`);
    } else if (savedSearch.priceMin != null) {
      parts.push(`od ${formatPln(savedSearch.priceMin)}`);
    } else if (savedSearch.priceMax != null) {
      parts.push(`do ${formatPln(savedSearch.priceMax)}`);
    }
  }

  if (!(savedSearch.wtorny && savedSearch.pierwotny)) {
    parts.push(savedSearch.wtorny ? 'Wtórny' : savedSearch.pierwotny ? 'Pierwotny' : 'Brak rynku');
  }

  if (savedSearch.noFeeOnly) {
    parts.push('Bez prowizji');
  }

  const moreCount = Object.keys(savedSearch.more).length;
  if (moreCount > 0) {
    parts.push(`+${moreCount} filtrów`);
  }

  return parts.join(' • ');
}

function applySavedSearchToFilters(filters: FilterState, savedSearch: SavedSearchRecord): void {
  filters.setCity(savedSearch.city);
  filters.setCityInput(savedSearch.city);
  filters.setPropertyType(savedSearch.propertyType as FilterState['propertyType']);
  filters.setPriceMin(savedSearch.priceMin == null ? '' : String(savedSearch.priceMin));
  filters.setPriceMax(savedSearch.priceMax == null ? '' : String(savedSearch.priceMax));
  filters.setAreaMin(savedSearch.areaMin == null ? '' : String(savedSearch.areaMin));
  filters.setAreaMax(savedSearch.areaMax == null ? '' : String(savedSearch.areaMax));
  filters.setRooms(savedSearch.rooms);
  filters.setWtorny(savedSearch.wtorny);
  filters.setPierwotny(savedSearch.pierwotny);
  filters.setNoFeeOnly(savedSearch.noFeeOnly);
  filters.setMore({ ...savedSearch.more });
}

function MegaSearch({ filters, onOpenMap, onSearch, mode = 'buy' }: {
  filters: FilterState;
  onOpenMap: (city: string, mode: 'buy' | 'rent') => void;
  onSearch?: () => void;
  mode?: 'buy' | 'rent';
}) {
  const {
    cityInput, setCityInput, city, setCity,
    propertyType, setPropertyType,
    priceMin, setPriceMin, priceMax, setPriceMax,
    areaMin, setAreaMin, areaMax, setAreaMax,
    rooms, setRooms, wtorny, setWtorny, pierwotny, setPierwotny,
    more, setMore,
    pMin, pMax, aMin, aMax,
    priceLabel, areaLabel, roomsShort, marketLabel,
    chips, resetAll,
  } = filters;

  const [recent, setRecent] = useState<string[]>(DEFAULT_RECENT);
  const [openField, setOpenField] = useState<'lokalizacja' | 'rodzaj' | 'cena' | 'metraz' | 'pokoje' | 'rynek' | 'more' | null>(null);
  const [suggestions, setSuggestions] = useState<PlaceSuggestion[]>([]);
  const [suggestLoading, setSuggestLoading] = useState(false);
  const skipNextFetch = useRef(false);
  const searchRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!openField) {
      return;
    }
    const handler = (event: MouseEvent) => {
      if (searchRef.current && !searchRef.current.contains(event.target as Node)) {
        setOpenField(null);
      }
    };
    document.addEventListener('mousedown', handler);
    return () => document.removeEventListener('mousedown', handler);
  }, [openField]);

  useEffect(() => {
    if (skipNextFetch.current) {
      skipNextFetch.current = false;
      return;
    }
    const query = cityInput.trim();
    if (query.length < 2) {
      setSuggestions([]);
      setSuggestLoading(false);
      return;
    }
    const controller = new AbortController();
    setSuggestLoading(true);
    const timer = window.setTimeout(() => {
      fetchPlaceSuggestions(query, controller.signal)
        .then((places) => setSuggestions(places))
        .catch(() => { /* ignore aborts */ })
        .finally(() => setSuggestLoading(false));
    }, 300);
    return () => {
      controller.abort();
      window.clearTimeout(timer);
    };
  }, [cityInput]);

  const toggleField = (field: 'lokalizacja' | 'rodzaj' | 'cena' | 'metraz' | 'pokoje' | 'rynek' | 'more') => {
    setOpenField((current) => (current === field ? null : field));
  };

  const clearCity = () => {
    filters.clearCity();
    setSuggestions([]);
  };

  const addRecent = (label: string) => {
    const trimmed = label.trim();
    if (!trimmed) {
      return;
    }
    setRecent((current) => [trimmed, ...current.filter((item) => item !== trimmed)].slice(0, 4));
  };

  const pickCity = (label: string) => {
    skipNextFetch.current = true;
    setCityInput(label);
    setCity(label.split(/[\s,]+/)[0] ?? label);
    setSuggestions([]);
    addRecent(label);
  };

  const pickPlace = (place: PlaceSuggestion) => {
    skipNextFetch.current = true;
    setCityInput(place.label);
    setCity(place.label);
    setSuggestions([]);
    addRecent(place.label);
    setOpenField(null);
  };

  const applySearch = () => {
    if (suggestions.length > 0) {
      pickPlace(suggestions[0]);
      onSearch?.();
      return;
    }
    const trimmed = cityInput.trim();
    setCity(trimmed.split(/[\s,]+/)[0] ?? '');
    if (trimmed) {
      addRecent(trimmed);
    }
    setSuggestions([]);
    setOpenField(null);
    onSearch?.();
  };

  const isRentMode = mode === 'rent';
  const priceSliderMax = isRentMode ? RENT_PRICE_SLIDER_MAX : PRICE_SLIDER_MAX;
  const priceSliderStep = isRentMode ? RENT_PRICE_SLIDER_STEP : PRICE_SLIDER_STEP;
  const pricePresets = isRentMode ? RENT_PRICE_PRESETS : PRICE_PRESETS;
  const sliderLow = pMin ?? 0;
  const sliderHigh = pMax ?? priceSliderMax;
  const onSlider = (low: number, high: number) => {
    setPriceMin(low <= 0 ? '' : String(low));
    setPriceMax(high >= priceSliderMax ? '' : String(high));
  };

  const areaSliderLow = aMin ?? 0;
  const areaSliderHigh = aMax ?? AREA_SLIDER_MAX;
  const onAreaSlider = (low: number, high: number) => {
    setAreaMin(low <= 0 ? '' : String(low));
    setAreaMax(high >= AREA_SLIDER_MAX ? '' : String(high));
  };

  const cityPills = cityInput.trim()
    ? POLISH_CITIES.filter((name) => name.toLowerCase().includes(cityInput.trim().toLowerCase())).slice(0, 8)
    : POPULAR_CITIES;
  const moreActiveCount = Object.values(more).filter((value) => value && !value.startsWith('Dowoln')).length;

  return (
          <div className={`mega-search ${openField ? 'open' : ''}`} ref={searchRef}>
            <div className="mega-bar">
              <div className="mega-cell">
              <div className={`mega-field ${city ? 'has-value' : ''} ${openField === 'lokalizacja' ? 'active' : ''}`} role="button" tabIndex={0} onClick={() => toggleField('lokalizacja')}>
                <Icon name="pin" />
                <span>
                  <small>Lokalizacja</small>
                  <strong>{cityInput || 'Cała Polska'}</strong>
                </span>
                {cityInput && (
                  <span
                    className="mega-clear"
                    role="button"
                    tabIndex={0}
                    aria-label="Wyczyść lokalizację"
                    onClick={(event) => { event.stopPropagation(); clearCity(); }}
                  >
                    ×
                  </span>
                )}
              </div>
              {openField === 'lokalizacja' && (
              <div className="mega-pop mega-pop-location">
                <div className="mega-col">
                  <div className="mega-city-search">
                    <Icon name="search" />
                    <input
                      type="text"
                      autoFocus
                      placeholder="Wpisz miasto, dzielnicę lub ulicę"
                      value={cityInput}
                      onChange={(event) => setCityInput(event.target.value)}
                      onKeyDown={(event) => { if (event.key === 'Enter') { applySearch(); } }}
                    />
                    {suggestLoading && <span className="map-suggest-spinner" aria-hidden="true" />}
                  </div>

                  {cityInput.trim().length >= 2 ? (
                    <div className="mega-suggest">
                      {suggestions.map((place) => (
                        <button type="button" key={place.id} onClick={() => pickPlace(place)}>
                          <Icon name="pin" />
                          <span>
                            <strong>{place.label}</strong>
                            {place.sublabel && <small>{place.sublabel}</small>}
                          </span>
                        </button>
                      ))}
                      {suggestions.length === 0 && !suggestLoading && (
                        <p className="map-suggest-empty">Brak wyników dla „{cityInput.trim()}"</p>
                      )}
                      <button
                        type="button"
                        className="mega-map-link"
                        onClick={() => { setOpenField(null); onOpenMap(city || cityInput.trim(), mode); }}
                      >
                        <Icon name="pin" />
                        <span>Sprawdź na mapie</span>
                        <Icon name="chevron" />
                      </button>
                    </div>
                  ) : (
                  <>
                  <button
                    type="button"
                    className="mega-map-link"
                    onClick={() => { setOpenField(null); onOpenMap(city || cityInput.trim(), mode); }}
                  >
                    <Icon name="pin" />
                    <span>Sprawdź na mapie</span>
                    <Icon name="chevron" />
                  </button>

                  <p className="mega-col-label">Popularne miasta</p>
                  <div className="mega-city-pills">
                    {cityPills.map((name) => (
                      <button
                        type="button"
                        key={name}
                        className={city === name ? 'active' : ''}
                        onClick={() => { pickCity(name); setOpenField(null); }}
                      >
                        {name}
                      </button>
                    ))}
                  </div>

                  <p className="mega-col-label">Ostatnio wyszukiwane</p>
                  <div className="mega-recent">
                    {recent.map((item) => (
                      <button type="button" key={item} onClick={() => { pickCity(item); setOpenField(null); }}>
                        <Icon name="pin" />
                        {item}
                      </button>
                    ))}
                  </div>
                  </>
                  )}
                </div>
              </div>
              )}
              </div>

              <div className="mega-cell">
              <button type="button" className={`mega-field ${openField === 'rodzaj' ? 'active' : ''}`} onClick={() => toggleField('rodzaj')}>
                <Icon name="building" />
                <span>
                  <small>Rodzaj nieruchomości</small>
                  <strong>{propertyType}</strong>
                </span>
                <Icon name="chevron" />
              </button>
              {openField === 'rodzaj' && (
              <div className="mega-pop mega-pop-property">
                <div className="mega-col">
                  <p className="mega-col-label">Rodzaj nieruchomości</p>
                  <div className="mega-property-options">
                    {propertyTypeOptions.map((option) => (
                      <button
                        type="button"
                        key={option}
                        className={propertyType === option ? 'active' : ''}
                        onClick={() => { setPropertyType(option); setOpenField(null); }}
                      >
                        {option}
                      </button>
                    ))}
                  </div>
                </div>
              </div>
              )}
              </div>

              <div className="mega-cell">
              <button type="button" className={`mega-field ${pMin != null || pMax != null ? 'has-value' : ''} ${openField === 'cena' ? 'active' : ''}`} onClick={() => toggleField('cena')}>
                <span>
                  <small>Cena</small>
                  <strong>{priceLabel}</strong>
                </span>
                <Icon name="chevron" />
              </button>
              {openField === 'cena' && (
              <div className="mega-pop mega-pop-price">
                <div className="mega-col">
                  <p className="mega-col-label">{isRentMode ? 'Czynsz miesięczny (zł)' : 'Cena (zł)'}</p>
                  <div className="mega-price-inputs">
                    <label>
                      <span>Od</span>
                      <input inputMode="numeric" placeholder="0" value={priceMin} onChange={(event) => setPriceMin(event.target.value)} />
                    </label>
                    <label>
                      <span>Do</span>
                      <input inputMode="numeric" placeholder="bez limitu" value={priceMax} onChange={(event) => setPriceMax(event.target.value)} />
                    </label>
                  </div>

                  <DualRange
                    min={0}
                    max={priceSliderMax}
                    step={priceSliderStep}
                    low={sliderLow}
                    high={sliderHigh}
                    onChange={onSlider}
                  />

                  <p className="mega-col-label">Popularne zakresy</p>
                  <div className="mega-price-presets">
                    {pricePresets.map((preset) => (
                      <button
                        type="button"
                        key={preset.value}
                        className={pMax === preset.value && pMin == null ? 'active' : ''}
                        onClick={() => { setPriceMin(''); setPriceMax(String(preset.value)); }}
                      >
                        {preset.label}
                      </button>
                    ))}
                  </div>

                  <button type="button" className="mega-apply" onClick={() => setOpenField(null)}>
                    Zastosuj
                  </button>
                </div>
              </div>
              )}
              </div>

              <div className="mega-cell">
              <button type="button" className={`mega-field ${aMin != null || aMax != null ? 'has-value' : ''} ${openField === 'metraz' ? 'active' : ''}`} onClick={() => toggleField('metraz')}>
                <span>
                  <small>Metraż</small>
                  <strong>{areaLabel}</strong>
                </span>
                <Icon name="chevron" />
              </button>
              {openField === 'metraz' && (
              <div className="mega-pop mega-pop-price">
                <div className="mega-col">
                  <p className="mega-col-label">Metraż (m²)</p>
                  <div className="mega-price-inputs">
                    <label>
                      <span>Od</span>
                      <input inputMode="numeric" placeholder="0" value={areaMin} onChange={(event) => setAreaMin(event.target.value)} />
                    </label>
                    <label>
                      <span>Do</span>
                      <input inputMode="numeric" placeholder="bez limitu" value={areaMax} onChange={(event) => setAreaMax(event.target.value)} />
                    </label>
                  </div>

                  <DualRange
                    min={0}
                    max={AREA_SLIDER_MAX}
                    step={AREA_SLIDER_STEP}
                    low={areaSliderLow}
                    high={areaSliderHigh}
                    onChange={onAreaSlider}
                  />

                  <p className="mega-col-label">Popularne zakresy</p>
                  <div className="mega-price-presets">
                    {AREA_PRESETS.map((preset) => {
                      const active = (preset.min === 0 ? aMin == null : aMin === preset.min)
                        && (preset.max === 0 ? aMax == null : aMax === preset.max);
                      return (
                        <button
                          type="button"
                          key={preset.label}
                          className={active ? 'active' : ''}
                          onClick={() => {
                            setAreaMin(preset.min === 0 ? '' : String(preset.min));
                            setAreaMax(preset.max === 0 ? '' : String(preset.max));
                          }}
                        >
                          {preset.label}
                        </button>
                      );
                    })}
                  </div>

                  <button type="button" className="mega-apply" onClick={() => setOpenField(null)}>
                    Zastosuj
                  </button>
                </div>
              </div>
              )}
              </div>

              <div className="mega-cell">
              <button type="button" className={`mega-field ${rooms != null ? 'has-value' : ''} ${openField === 'pokoje' ? 'active' : ''}`} onClick={() => toggleField('pokoje')}>
                <span>
                  <small>Pokoje</small>
                  <strong>{roomsShort}</strong>
                </span>
                <Icon name="chevron" />
              </button>
              {openField === 'pokoje' && (
              <div className="mega-pop mega-pop-rooms">
                <div className="mega-col">
                  <p className="mega-col-label">Liczba pokoi</p>
                  <div className="mega-rooms-choices">
                    <button
                      type="button"
                      className={rooms == null ? 'active' : ''}
                      onClick={() => setRooms(null)}
                    >
                      Dowolna
                    </button>
                    {ROOM_OPTIONS.map((value) => (
                      <button
                        type="button"
                        key={value}
                        className={rooms === value ? 'active' : ''}
                        onClick={() => setRooms(value)}
                      >
                        {value === 5 ? '5+' : value}
                      </button>
                    ))}
                  </div>
                  <button type="button" className="mega-apply" onClick={() => setOpenField(null)}>
                    Zastosuj
                  </button>
                </div>
              </div>
              )}
              </div>

              <div className="mega-cell">
              <button type="button" className={`mega-field ${!(wtorny && pierwotny) ? 'has-value' : ''} ${openField === 'rynek' ? 'active' : ''}`} onClick={() => toggleField('rynek')}>
                <span>
                  <small>Rynek</small>
                  <strong>{marketLabel}</strong>
                </span>
                <Icon name="chevron" />
              </button>
              {openField === 'rynek' && (
              <div className="mega-pop mega-pop-market">
                <div className="mega-col">
                  <p className="mega-col-label">Rynek</p>
                  <div className="mega-market-choices">
                    <label className="mega-check">
                      <input type="checkbox" checked={wtorny} onChange={(event) => setWtorny(event.target.checked)} />
                      <span>Rynek wtórny</span>
                    </label>
                    <label className="mega-check">
                      <input type="checkbox" checked={pierwotny} onChange={(event) => setPierwotny(event.target.checked)} />
                      <span>Rynek pierwotny</span>
                    </label>
                  </div>
                  <button type="button" className="mega-apply" onClick={() => setOpenField(null)}>
                    Zastosuj
                  </button>
                </div>
              </div>
              )}
              </div>

              <div className="mega-cell mega-cell-more">
              <button type="button" className={`mega-more ${moreActiveCount > 0 ? 'has-value' : ''} ${openField === 'more' ? 'active' : ''}`} onClick={() => toggleField('more')}>
                <svg viewBox="0 0 24 24" aria-hidden="true">
                  <path d="M4 6h10M18 6h2M4 12h2M10 12h10M4 18h12M20 18h0" />
                  <path d="M16 4v4M8 10v4M14 16v4" />
                </svg>
                <span>Więcej filtrów</span>
                {moreActiveCount > 0 && <em>{moreActiveCount}</em>}
              </button>
              {openField === 'more' && (
              <div className="mega-pop mega-pop-more">
                <div className="mega-col">
                  <div className="mega-more-scroll">
                    <div className="mega-more-section">
                      <p className="mega-col-label">Dodatkowe filtry</p>
                      <div className="mega-more-grid">
                        {MORE_FILTERS.map((filter) => (
                          <label className="mega-select" key={filter.key}>
                            <span>{filter.label}</span>
                            <select
                              value={more[filter.key] ?? filter.options[0]}
                              onChange={(event) => setMore((current) => ({ ...current, [filter.key]: event.target.value }))}
                            >
                              {filter.options.map((option) => (
                                <option key={option}>{option}</option>
                              ))}
                            </select>
                          </label>
                        ))}
                      </div>
                    </div>
                  </div>
                  <div className="mega-more-actions">
                    <button type="button" className="mega-apply" onClick={applySearch}>
                      Pokaż wyniki
                    </button>
                  </div>
                </div>
              </div>
              )}
              </div>

              <button type="button" className="mega-search-btn" onClick={applySearch}>
                <span>Szukaj</span>
                <Icon name="search" />
              </button>
            </div>
            {chips.length > 0 && (
              <div className="mega-footer">
                <button type="button" className="mega-clear-all" onClick={resetAll}>
                  Wyczyść filtry
                </button>
              </div>
            )}
          </div>
  );
}

type HistoryPropertyType = 'Mieszkanie' | 'Dom' | 'Lokal użytkowy';
type HistoryMarketType = 'Wtórny i pierwotny' | 'Wtórny' | 'Pierwotny';
type HistoryPeriod = '2026';

type PriceHistoryMarket = {
  key: string;
  label: string;
  city: string;
  region: string;
  apartmentM2: number;
  trend24: number;
  volatility: number;
  aliases?: string[];
};

const HISTORY_PRICE_MARKETS: PriceHistoryMarket[] = [
  { key: 'warszawa-mokotow', label: 'Warszawa, Mokotów', city: 'Warszawa', region: 'mazowieckie', apartmentM2: 16842, trend24: 0.086, volatility: 0.032, aliases: ['mokotów'] },
  { key: 'warszawa-srodmiescie', label: 'Warszawa, Śródmieście', city: 'Warszawa', region: 'mazowieckie', apartmentM2: 18932, trend24: 0.094, volatility: 0.036, aliases: ['śródmieście warszawa'] },
  { key: 'warszawa-wola', label: 'Warszawa, Wola', city: 'Warszawa', region: 'mazowieckie', apartmentM2: 16123, trend24: 0.083, volatility: 0.031, aliases: ['wola'] },
  { key: 'warszawa-praga-poludnie', label: 'Warszawa, Praga-Południe', city: 'Warszawa', region: 'mazowieckie', apartmentM2: 13254, trend24: 0.071, volatility: 0.029, aliases: ['praga południe', 'praga-poludnie'] },
  { key: 'warszawa-bielany', label: 'Warszawa, Bielany', city: 'Warszawa', region: 'mazowieckie', apartmentM2: 12876, trend24: 0.067, volatility: 0.027, aliases: ['bielany'] },
  { key: 'krakow-zablocie', label: 'Kraków, Zabłocie', city: 'Kraków', region: 'małopolskie', apartmentM2: 15840, trend24: 0.091, volatility: 0.034, aliases: ['zabłocie'] },
  { key: 'krakow-stare-miasto', label: 'Kraków, Stare Miasto', city: 'Kraków', region: 'małopolskie', apartmentM2: 17490, trend24: 0.088, volatility: 0.037 },
  { key: 'krakow-krowodrza', label: 'Kraków, Krowodrza', city: 'Kraków', region: 'małopolskie', apartmentM2: 14320, trend24: 0.079, volatility: 0.031 },
  { key: 'wroclaw-krzyki', label: 'Wrocław, Krzyki', city: 'Wrocław', region: 'dolnośląskie', apartmentM2: 13480, trend24: 0.082, volatility: 0.032, aliases: ['krzyki'] },
  { key: 'wroclaw-stare-miasto', label: 'Wrocław, Stare Miasto', city: 'Wrocław', region: 'dolnośląskie', apartmentM2: 14750, trend24: 0.084, volatility: 0.034 },
  { key: 'gdansk-wrzeszcz', label: 'Gdańsk, Wrzeszcz', city: 'Gdańsk', region: 'pomorskie', apartmentM2: 14320, trend24: 0.087, volatility: 0.036, aliases: ['wrzeszcz'] },
  { key: 'gdansk-srodmiescie', label: 'Gdańsk, Śródmieście', city: 'Gdańsk', region: 'pomorskie', apartmentM2: 16080, trend24: 0.089, volatility: 0.039 },
  { key: 'gdynia-redlowo', label: 'Gdynia, Redłowo', city: 'Gdynia', region: 'pomorskie', apartmentM2: 13960, trend24: 0.075, volatility: 0.034 },
  { key: 'sopot-centrum', label: 'Sopot, Centrum', city: 'Sopot', region: 'pomorskie', apartmentM2: 18850, trend24: 0.079, volatility: 0.043 },
  { key: 'poznan-jezyce', label: 'Poznań, Jeżyce', city: 'Poznań', region: 'wielkopolskie', apartmentM2: 11980, trend24: 0.064, volatility: 0.028, aliases: ['jeżyce'] },
  { key: 'poznan-grunwald', label: 'Poznań, Grunwald', city: 'Poznań', region: 'wielkopolskie', apartmentM2: 11150, trend24: 0.058, volatility: 0.026 },
  { key: 'lodz-srodmiescie', label: 'Łódź, Śródmieście', city: 'Łódź', region: 'łódzkie', apartmentM2: 9350, trend24: 0.054, volatility: 0.028 },
  { key: 'lodz-widzew', label: 'Łódź, Widzew', city: 'Łódź', region: 'łódzkie', apartmentM2: 8420, trend24: 0.047, volatility: 0.026 },
  { key: 'katowice-koszutka', label: 'Katowice, Koszutka', city: 'Katowice', region: 'śląskie', apartmentM2: 10320, trend24: 0.059, volatility: 0.029 },
  { key: 'rzeszow-centrum', label: 'Rzeszów, Centrum', city: 'Rzeszów', region: 'podkarpackie', apartmentM2: 10380, trend24: 0.069, volatility: 0.031 },
  { key: 'lublin-centrum', label: 'Lublin, Centrum', city: 'Lublin', region: 'lubelskie', apartmentM2: 10120, trend24: 0.061, volatility: 0.028 },
  { key: 'bialystok-centrum', label: 'Białystok, Centrum', city: 'Białystok', region: 'podlaskie', apartmentM2: 9580, trend24: 0.053, volatility: 0.025 },
  { key: 'szczecin-centrum', label: 'Szczecin, Centrum', city: 'Szczecin', region: 'zachodniopomorskie', apartmentM2: 9840, trend24: 0.056, volatility: 0.027 },
  { key: 'bydgoszcz-srodmiescie', label: 'Bydgoszcz, Śródmieście', city: 'Bydgoszcz', region: 'kujawsko-pomorskie', apartmentM2: 8230, trend24: 0.044, volatility: 0.025 },
  { key: 'torun-centrum', label: 'Toruń, Centrum', city: 'Toruń', region: 'kujawsko-pomorskie', apartmentM2: 9140, trend24: 0.049, volatility: 0.026 },
  { key: 'olsztyn-centrum', label: 'Olsztyn, Centrum', city: 'Olsztyn', region: 'warmińsko-mazurskie', apartmentM2: 9120, trend24: 0.05, volatility: 0.026 },
  { key: 'opole-centrum', label: 'Opole, Centrum', city: 'Opole', region: 'opolskie', apartmentM2: 8840, trend24: 0.047, volatility: 0.025 },
  { key: 'kielce-centrum', label: 'Kielce, Centrum', city: 'Kielce', region: 'świętokrzyskie', apartmentM2: 8060, trend24: 0.042, volatility: 0.024 },
  { key: 'zielona-gora-centrum', label: 'Zielona Góra, Centrum', city: 'Zielona Góra', region: 'lubuskie', apartmentM2: 8120, trend24: 0.043, volatility: 0.025 },
  { key: 'zakopane-centrum', label: 'Zakopane, Centrum', city: 'Zakopane', region: 'małopolskie', apartmentM2: 17200, trend24: 0.081, volatility: 0.045 },
];

const HISTORY_CITY_BASE_PRICES: Record<string, number> = {
  Warszawa: 16000, Kraków: 14500, Wrocław: 12600, Gdańsk: 13600, Gdynia: 12700, Sopot: 17800,
  Poznań: 10800, Łódź: 8500, Katowice: 9700, Rzeszów: 9800, Lublin: 9300, Białystok: 8900,
  Szczecin: 9200, Bydgoszcz: 7600, Toruń: 8500, Kielce: 7400, Olsztyn: 8500, Opole: 8200,
  Zielona: 7800, 'Zielona Góra': 7800, Częstochowa: 6900, Radom: 6700, Gliwice: 7800,
  Zabrze: 6100, Rybnik: 6200, Tychy: 7600, Bielsko: 8600, 'Bielsko-Biała': 8600,
  Płock: 7100, Tarnów: 7200, Koszalin: 7900, Kalisz: 7100, Legnica: 7300, Słupsk: 7400,
  Nowy: 7600, 'Nowy Sącz': 7600, Jelenia: 7400, 'Jelenia Góra': 7400, Piaseczno: 11200,
  Pruszków: 10400, Legionowo: 9400, Wieliczka: 9300, Zakopane: 16000, Kołobrzeg: 12200,
};

const HISTORY_POPULAR_MARKETS = ['Warszawa, Mokotów', 'Kraków, Zabłocie', 'Wrocław, Krzyki', 'Gdańsk, Wrzeszcz', 'Poznań, Jeżyce'];
const HISTORY_PROPERTY_MULTIPLIERS: Record<HistoryPropertyType, number> = { Mieszkanie: 1, Dom: 0.78, 'Lokal użytkowy': 1.12 };
const HISTORY_MARKET_MULTIPLIERS: Record<HistoryMarketType, number> = { 'Wtórny i pierwotny': 1, Wtórny: 0.97, Pierwotny: 1.08 };
const HISTORY_2026_SOURCE_NOTE = 'Dane orientacyjne: wykres pokazuje model rynku 2026 na podstawie lokalizacji i korekt cen za m². Nie jest to gwarancja ceny transakcyjnej ani oficjalna wycena.';
const HISTORY_LOCATION_TYPE_MULTIPLIERS: Record<string, number> = {
  dzielnica: 1,
  miasto: 0.96,
  town: 0.86,
  miejscowość: 0.78,
  gmina: 0.76,
  powiat: 0.74,
  wieś: 0.62,
  przysiółek: 0.56,
};
const HISTORY_YEAR = 2026;
const HISTORY_YEAR_MONTHS = ['sty', 'lut', 'mar', 'kwi', 'maj', 'cze', 'lip', 'sie', 'wrz', 'paź', 'lis', 'gru'];
const HISTORY_PERIOD_OPTIONS: { value: HistoryPeriod; label: string }[] = [
  { value: '2026', label: '2026 do dziś' },
];

function normalizeHistoryText(value: string) {
  return value.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/ł/g, 'l').replace(/[^a-z0-9]+/g, ' ').trim();
}

function historyLocationTypeFromText(value: string, city?: string | null) {
  const normalized = normalizeHistoryText(value);
  if (normalized.includes('dzielnica') || normalized.includes('osiedle')) {
    return 'dzielnica';
  }
  if (normalized.includes('wies') || normalized.includes('przysiolek') || normalized.includes('hamlet') || normalized.includes('osada')) {
    return normalized.includes('przysiolek') || normalized.includes('hamlet') ? 'przysiółek' : 'wieś';
  }
  if (normalized.includes('powiat')) {
    return 'powiat';
  }
  if (normalized.includes('gmina')) {
    return 'gmina';
  }
  if (normalized.includes('miejscowosc')) {
    return 'miejscowość';
  }
  if (normalized.includes('miasto') || city) {
    return 'miasto';
  }
  return 'miejscowość';
}

function historyFindValuationCity(value: string) {
  const normalized = normalizeHistoryText(value);
  return Object.keys(VALUATION_PRICE_PER_SQM_BY_CITY).find((city) => {
    const normalizedCity = normalizeHistoryText(city);
    return normalized === normalizedCity || normalized.includes(normalizedCity) || normalizedCity.includes(normalized);
  }) ?? null;
}

function historyFindKnownCity(value: string) {
  const normalized = normalizeHistoryText(value);
  return POLISH_CITIES.find((city) => {
    const normalizedCity = normalizeHistoryText(city);
    return normalized === normalizedCity || normalized.includes(normalizedCity) || normalizedCity.includes(normalized);
  }) ?? null;
}

function historyFindRegion(value: string) {
  const normalized = normalizeHistoryText(value);
  return Object.keys(VALUATION_PRICE_PER_SQM_BY_REGION).find((region) => normalized.includes(normalizeHistoryText(region))) ?? null;
}

function estimateHistoryLocationPrice(label: string, sublabel = '') {
  const locationText = [label, sublabel].filter(Boolean).join(', ');
  const normalized = normalizeHistoryText(locationText);
  const valuationCity = historyFindValuationCity(locationText);
  const knownCity = valuationCity ?? historyFindKnownCity(locationText);
  const region = historyFindRegion(locationText);
  const locationType = historyLocationTypeFromText(locationText, knownCity);
  const locationMultiplier = HISTORY_LOCATION_TYPE_MULTIPLIERS[locationType] ?? 0.78;
  const seededAdjustment = 0.96 + (normalized.split('').reduce((sum, char) => sum + char.charCodeAt(0), 0) % 9) / 100;

  if (valuationCity) {
    const exactPrice = VALUATION_PRICE_PER_SQM_BY_CITY[valuationCity];
    const cityMultiplier = locationType === 'dzielnica' ? 1 : locationType === 'miasto' ? 1 : locationMultiplier;
    return {
      price: Math.round((exactPrice * cityMultiplier * seededAdjustment) / 50) * 50,
      region: region ?? 'Polska',
      locationType,
    };
  }

  const cityIndex = knownCity ? POLISH_CITIES.findIndex((city) => normalizeHistoryText(city) === normalizeHistoryText(knownCity)) : -1;
  if (cityIndex >= 0) {
    const regionalBase = region ? VALUATION_PRICE_PER_SQM_BY_REGION[region] : VALUATION_NATIONAL_BASE_PER_SQM;
    const cityTierMultiplier = cityIndex < 8 ? 1.24 : cityIndex < 30 ? 1.12 : cityIndex < 80 ? 1.02 : 0.92;
    return {
      price: Math.round((regionalBase * cityTierMultiplier * seededAdjustment) / 50) * 50,
      region: region ?? 'Polska',
      locationType: 'miasto',
    };
  }

  if (region) {
    return {
      price: Math.round((VALUATION_PRICE_PER_SQM_BY_REGION[region] * locationMultiplier * seededAdjustment) / 50) * 50,
      region,
      locationType,
    };
  }

  const cityExact = Object.entries(HISTORY_CITY_BASE_PRICES).find(([city]) => normalizeHistoryText(city) === normalized || normalized.startsWith(`${normalizeHistoryText(city)} `));
  if (cityExact) {
    return { price: cityExact[1], region: 'Polska', locationType: 'miasto' };
  }

  return {
    price: Math.round((VALUATION_NATIONAL_BASE_PER_SQM * locationMultiplier * seededAdjustment) / 50) * 50,
    region: 'Polska',
    locationType,
  };
}

function findHistoryMarket(label: string) {
  const normalized = normalizeHistoryText(label);
  return HISTORY_PRICE_MARKETS.find((market) => {
    const names = [market.label, market.city, ...(market.aliases ?? [])].map(normalizeHistoryText);
    return names.some((name) => normalized === name || normalized.includes(name) || name.includes(normalized));
  });
}

function createHistoryMarketFromPlace(label: string, sublabel = 'Polska'): PriceHistoryMarket {
  const city = label.split(',')[0].trim() || label;
  const estimate = estimateHistoryLocationPrice(label, sublabel);
  const normalized = normalizeHistoryText(`${label}-${sublabel}`) || 'polska';
  const seed = normalized.split('').reduce((sum, char) => sum + char.charCodeAt(0), 0);
  const sublabelParts = sublabel.split(',');
  const sublabelTail = sublabelParts[sublabelParts.length - 1]?.trim();
  return {
    key: `custom-${normalized.replace(/\s+/g, '-')}`,
    label: [label, sublabel.includes('województwo') ? '' : sublabelTail].filter(Boolean).join(', '),
    city,
    region: estimate.region,
    apartmentM2: Math.round(estimate.price * (0.98 + (seed % 5) / 100)),
    trend24: 0.035 + (seed % 52) / 1000,
    volatility: 0.02 + (seed % 22) / 1000,
  };
}

function historyLocalSuggestions(query: string): PlaceSuggestion[] {
  const normalized = normalizeHistoryText(query);
  const markets = HISTORY_PRICE_MARKETS
    .filter((market) => [market.label, market.city, ...(market.aliases ?? [])].some((value) => normalizeHistoryText(value).includes(normalized)))
    .slice(0, 8)
    .map((market, index) => ({ id: -index - 1, label: market.label, sublabel: `${market.city}, ${market.region}`, lat: 0, lng: 0 }));
  if (markets.length >= 8) {
    return markets;
  }
  const cities = POLISH_CITIES
    .filter((city) => normalizeHistoryText(city).includes(normalized))
    .slice(0, 8 - markets.length)
    .map((city, index) => ({ id: -100 - index, label: city, sublabel: 'miasto, Polska', lat: 0, lng: 0 }));
  return [...markets, ...cities];
}

function mergeHistorySuggestions(local: PlaceSuggestion[], remote: PlaceSuggestion[]) {
  const seen = new Set<string>();
  return [...local, ...remote].filter((place) => {
    const key = normalizeHistoryText(`${place.label} ${place.sublabel}`);
    if (seen.has(key)) {
      return false;
    }
    seen.add(key);
    return true;
  }).slice(0, 8);
}

function resolveHistoryMarket(label: string, sublabel?: string) {
  return findHistoryMarket(label) ?? createHistoryMarketFromPlace(label, sublabel);
}

function formatM2(value: number) {
  return `${formatPln(Math.round(value))}/m²`;
}

function historyToday() {
  const today = new Date();
  if (today.getFullYear() < HISTORY_YEAR) {
    return new Date(HISTORY_YEAR, 0, 1);
  }
  if (today.getFullYear() > HISTORY_YEAR) {
    return new Date(HISTORY_YEAR, 11, 31);
  }
  return today;
}

function historyMonthLabels(today = historyToday()) {
  const monthIndex = Math.min(11, Math.max(0, today.getMonth()));
  return HISTORY_YEAR_MONTHS.slice(0, monthIndex + 1);
}

function historyTodayLabel(today = historyToday()) {
  return new Intl.DateTimeFormat('pl-PL', { day: '2-digit', month: '2-digit', year: 'numeric' }).format(today);
}

function generateHistorySeries(market: PriceHistoryMarket, propertyType: HistoryPropertyType, marketType: HistoryMarketType, period: HistoryPeriod, pointCount: number) {
  void period;
  const months = Math.max(1, pointCount);
  const latest = market.apartmentM2 * HISTORY_PROPERTY_MULTIPLIERS[propertyType] * HISTORY_MARKET_MULTIPLIERS[marketType];
  const elapsedMonths = Math.max(0, months - 1);
  const periodTrend = market.trend24 * (elapsedMonths / 24);
  const start = latest / (1 + periodTrend);
  return Array.from({ length: months }, (_, index) => {
    const progress = months === 1 ? 1 : index / (months - 1);
    const seasonal = Math.sin((index + market.key.length) * 0.82) * market.volatility * 0.34;
    const localDip = Math.exp(-Math.pow(progress - 0.32, 2) / 0.012) * market.volatility * -0.55;
    const recovery = progress > 0.66 ? (progress - 0.66) * market.volatility * 0.45 : 0;
    return Math.round(start + (latest - start) * progress + latest * (seasonal + localDip + recovery));
  });
}

function historyTicks(minPrice: number, maxPrice: number) {
  if (minPrice === 0 && maxPrice === 0) {
    return [0];
  }
  if (minPrice === maxPrice) {
    return [minPrice];
  }
  return [minPrice, maxPrice];
}

function historyRecentMeta(propertyType: HistoryPropertyType) {
  return `${propertyType} · cena za m²`;
}

function historyShortMarketLabel(market: PriceHistoryMarket) {
  const cityPrefix = `${market.city}, `;
  if (!market.label.startsWith(cityPrefix)) {
    return market.label;
  }
  const tail = market.label.slice(cityPrefix.length).trim();
  if (!tail || normalizeHistoryText(tail) === 'polska' || normalizeHistoryText(tail) === normalizeHistoryText(market.region)) {
    return market.city;
  }
  return tail;
}

function PriceHistoryPage({ user }: { user: AuthUser | null }) {
  const navigate = useNavigate();
  const [locationInput, setLocationInput] = useState('');
  const [selectedMarket, setSelectedMarket] = useState<PriceHistoryMarket | null>(null);
  const [propertyType, setPropertyType] = useState<HistoryPropertyType>('Mieszkanie');
  const [marketType, setMarketType] = useState<HistoryMarketType>('Wtórny i pierwotny');
  const [period, setPeriod] = useState<HistoryPeriod>('2026');
  const [createAlertFeedback, setCreateAlertFeedback] = useState<string | null>(null);
  const [cityAlertSubscriptions, setCityAlertSubscriptions] = useState<CityPriceAlertSubscription[]>([]);
  const [suggestions, setSuggestions] = useState<PlaceSuggestion[]>([]);
  const [suggestOpen, setSuggestOpen] = useState(false);
  const [suggestLoading, setSuggestLoading] = useState(false);
  const [hoveredHistoryIndex, setHoveredHistoryIndex] = useState<number | null>(null);
  const [recentMarkets, setRecentMarkets] = useState<PriceHistoryMarket[]>([HISTORY_PRICE_MARKETS[0], HISTORY_PRICE_MARKETS[6], HISTORY_PRICE_MARKETS[8]]);
  const skipNextFetch = useRef(false);
  const locationQuery = locationInput.trim();
  const localSuggestions = locationQuery.length >= 2 ? historyLocalSuggestions(locationQuery) : HISTORY_POPULAR_MARKETS.map((label, index) => {
    const market = resolveHistoryMarket(label);
    return { id: -200 - index, label: market.label, sublabel: `${market.city}, ${market.region}`, lat: 0, lng: 0 };
  });
  const shownSuggestions = mergeHistorySuggestions(localSuggestions, suggestions);
  const hasSelectedMarket = selectedMarket != null;
  const activeMarket = selectedMarket;
  const activeLocationLabel = selectedMarket?.label ?? 'wybranej lokalizacji';
  const todayLabel = historyTodayLabel();
  const monthLabels = historyMonthLabels();
  const priceHistoryPoints = activeMarket ? generateHistorySeries(activeMarket, propertyType, marketType, period, monthLabels.length) : Array.from({ length: monthLabels.length }, () => 0);
  const latestPrice = priceHistoryPoints[priceHistoryPoints.length - 1];
  const firstPrice = priceHistoryPoints[0];
  const minPrice = Math.min(...priceHistoryPoints);
  const maxPrice = Math.max(...priceHistoryPoints);
  const averagePrice = Math.round(priceHistoryPoints.reduce((sum, price) => sum + price, 0) / priceHistoryPoints.length);
  const changePercent = firstPrice > 0 ? ((latestPrice - firstPrice) / firstPrice) * 100 : 0;
  const changeValue = latestPrice - firstPrice;
  const minIndex = priceHistoryPoints.indexOf(minPrice);
  const maxIndex = priceHistoryPoints.indexOf(maxPrice);
  const tickValues = historyTicks(minPrice, maxPrice);
  const chartValues = priceHistoryPoints;
  const chartTicks = tickValues;
  const chartAxisMin = minPrice;
  const chartAxisMax = maxPrice;
  const compareMarkets = activeMarket
    ? HISTORY_PRICE_MARKETS
      .filter((market) => market.key !== activeMarket.key)
      .sort((left, right) => {
        const leftScore = (left.city === activeMarket.city ? -10000 : 0) + Math.abs(left.apartmentM2 - activeMarket.apartmentM2);
        const rightScore = (right.city === activeMarket.city ? -10000 : 0) + Math.abs(right.apartmentM2 - activeMarket.apartmentM2);
        return leftScore - rightScore;
      })
      .slice(0, 4)
    : HISTORY_PRICE_MARKETS.slice(0, 4);
  const displayedComparisons = activeMarket ? [activeMarket, ...compareMarkets] : compareMarkets;
  const trendLabel = changePercent >= 6 ? 'Trend wzrostowy' : changePercent >= 1 ? 'Rynek stabilny z lekkim wzrostem' : 'Korekta cenowa';
  const trendText = !hasSelectedMarket
    ? 'Wpisz i wybierz lokalizację, żeby zobaczyć realny przebieg cen za m² w modelu 2026.'
    : changePercent >= 6
    ? `Ceny w tej lokalizacji wzrosły o ${changePercent.toFixed(1).replace('.', ',')}% od stycznia do ${todayLabel}.`
    : changePercent >= 1
      ? `Ceny zmieniły się umiarkowanie: ${changePercent.toFixed(1).replace('.', ',')}% od stycznia do ${todayLabel}.`
      : `Widać spadek o ${Math.abs(changePercent).toFixed(1).replace('.', ',')}% od stycznia do ${todayLabel}.`;
  const chartWidth = 920;
  const chartHeight = 250;
  const chartPaddingTop = 86;
  const chartPaddingRight = 22;
  const chartPaddingBottom = 28;
  const chartPaddingLeft = 52;
  const chartInnerWidth = chartWidth - chartPaddingLeft - chartPaddingRight;
  const chartInnerHeight = chartHeight - chartPaddingTop - chartPaddingBottom;
  const pointCoords = chartValues.map((price, index) => {
    const x = chartPaddingLeft + (index / (chartValues.length - 1)) * chartInnerWidth;
    const range = chartAxisMax - chartAxisMin || 1;
    const y = chartPaddingTop + (1 - (price - chartAxisMin) / range) * chartInnerHeight;
    return { x, y, price, sourcePrice: priceHistoryPoints[index] };
  });
  const linePath = pointCoords.map((point, index) => `${index === 0 ? 'M' : 'L'} ${point.x.toFixed(1)} ${point.y.toFixed(1)}`).join(' ');
  const chartBaseY = chartHeight - chartPaddingBottom;
  const areaPath = `${linePath} L ${pointCoords[pointCoords.length - 1].x.toFixed(1)} ${chartBaseY} L ${chartPaddingLeft} ${chartBaseY} Z`;
  const hoveredPoint = hoveredHistoryIndex != null ? pointCoords[hoveredHistoryIndex] : null;
  const hoveredDate = hoveredHistoryIndex != null ? `${monthLabels[hoveredHistoryIndex]} ${HISTORY_YEAR}${hoveredHistoryIndex === monthLabels.length - 1 ? ` (do ${todayLabel})` : ''}` : '';
  const tooltipWidth = 132;
  const tooltipHeight = 54;
  const tooltipX = hoveredPoint ? Math.min(chartWidth - tooltipWidth - 8, Math.max(8, hoveredPoint.x - tooltipWidth / 2)) : 0;
  const tooltipY = hoveredPoint ? Math.max(8, hoveredPoint.y - tooltipHeight - 14) : 0;
  const selectedMarketAlreadyAlerted = selectedMarket
    ? cityAlertSubscriptions.some((item) => item.key === selectedMarket.key)
    : false;

  const handleHistoryChartMouseMove = (event: ReactMouseEvent<HTMLDivElement>) => {
    if (!hasSelectedMarket) {
      return;
    }
    const bounds = event.currentTarget.getBoundingClientRect();
    const svgX = ((event.clientX - bounds.left) / bounds.width) * chartWidth;
    const rawIndex = Math.round(((svgX - chartPaddingLeft) / chartInnerWidth) * (pointCoords.length - 1));
    const nextIndex = Math.min(pointCoords.length - 1, Math.max(0, rawIndex));
    setHoveredHistoryIndex(nextIndex);
  };

  useEffect(() => {
    if (!createAlertFeedback) {
      return;
    }
    const timeoutId = window.setTimeout(() => {
      setCreateAlertFeedback(null);
    }, 5000);
    return () => window.clearTimeout(timeoutId);
  }, [createAlertFeedback]);

  useEffect(() => {
    if (!user) {
      setCityAlertSubscriptions([]);
      return;
    }
    setCityAlertSubscriptions(readUserJsonStorage<CityPriceAlertSubscription[]>(user, CITY_PRICE_ALERTS_STORAGE_SUFFIX, []));
  }, [user]);

  useEffect(() => {
    if (skipNextFetch.current) {
      skipNextFetch.current = false;
      return;
    }
    const query = locationInput.trim();
    if (query.length < 2) {
      setSuggestions([]);
      setSuggestLoading(false);
      return;
    }
    const controller = new AbortController();
    setSuggestLoading(true);
    const timer = window.setTimeout(() => {
      fetchPlaceSuggestions(query, controller.signal)
        .then((places) => setSuggestions(places))
        .catch((error) => {
          if ((error as Error).name !== 'AbortError') {
            setSuggestions([]);
          }
        })
        .finally(() => setSuggestLoading(false));
    }, 300);
    return () => {
      controller.abort();
      window.clearTimeout(timer);
    };
  }, [locationInput]);

  const applyMarket = (market: PriceHistoryMarket) => {
    skipNextFetch.current = true;
    setSelectedMarket(market);
    setHoveredHistoryIndex(null);
    setLocationInput(market.label);
    setSuggestions([]);
    setSuggestOpen(false);
    setRecentMarkets((current) => [market, ...current.filter((item) => item.key !== market.key)].slice(0, 3));
  };

  const selectSuggestion = (place: PlaceSuggestion) => {
    applyMarket(resolveHistoryMarket(place.label, place.sublabel));
  };

  const applyLocation = () => {
    if (locationQuery.length < 2) {
      skipNextFetch.current = true;
      setSelectedMarket(null);
      setHoveredHistoryIndex(null);
      setSuggestions([]);
      setSuggestOpen(false);
      return;
    }
    const firstSuggestion = shownSuggestions[0];
    if (firstSuggestion) {
      selectSuggestion(firstSuggestion);
      return;
    }
    applyMarket(resolveHistoryMarket(locationInput));
  };

  const clearHistoryFilters = () => {
    skipNextFetch.current = true;
    setSelectedMarket(null);
    setHoveredHistoryIndex(null);
    setLocationInput('');
    setSuggestions([]);
    setSuggestOpen(false);
    setPropertyType('Mieszkanie');
    setMarketType('Wtórny i pierwotny');
    setPeriod('2026');
  };

  const createCityAlert = () => {
    if (!user) {
      setCreateAlertFeedback('Zaloguj się, aby dodać alert cenowy.');
      navigate(ROUTES.login);
      return;
    }
    if (!selectedMarket) {
      setCreateAlertFeedback('Najpierw wybierz lokalizację do monitorowania.');
      return;
    }

    const subscription: CityPriceAlertSubscription = {
      key: selectedMarket.key,
      label: selectedMarket.label,
      city: selectedMarket.city,
      region: selectedMarket.region,
      propertyType,
      marketType,
      createdAt: new Date().toISOString(),
    };

    setCityAlertSubscriptions((current) => {
      if (current.some((item) => item.key === subscription.key)) {
        setCreateAlertFeedback('Ten alert jest już aktywny.');
        return current;
      }
      const next = [subscription, ...current];
      writeUserJsonStorage(user, CITY_PRICE_ALERTS_STORAGE_SUFFIX, next);
      setCreateAlertFeedback('Alert cenowy został dodany. Znajdziesz go w panelu użytkownika.');
      return next;
    });
  };

  return (
    <section className="history-page" aria-label="Historia cen">
      <div className="history-shell">
        <div className="breadcrumb history-breadcrumb">
          <Link to={ROUTES.account}>Panel główny</Link>
          <Icon name="chevron" />
          <Link className="active" to={ROUTES.priceHistory}>Historia cen</Link>
        </div>

        <div className="history-title">
          <h1>Historia cen</h1>
          <p>Sprawdź, jak zmieniały się ceny mieszkań w czasie. Analizuj trendy i podejmuj lepsze decyzje.</p>
        </div>

        <section className="history-filter-panel" aria-label="Filtry historii cen">
          <div className="history-filter-grid">
            <label className="history-field location">
              <span>Lokalizacja</span>
              <div className={`history-location-picker ${suggestOpen ? 'active' : ''}`}>
                <button className={`history-location-display ${selectedMarket ? '' : 'empty'}`} type="button" onClick={() => setSuggestOpen((current) => !current)}>
                  <Icon name="pin" />
                  <span>
                    <small>Lokalizacja</small>
                    <strong>{selectedMarket?.label ?? 'Wpisz miasto, dzielnicę lub miejscowość'}</strong>
                  </span>
                  <Icon name="chevron" />
                </button>
                {suggestOpen && (
                  <div className="history-location-pop" onMouseDown={(event) => event.preventDefault()}>
                    <div className="history-location-search">
                      <Icon name="search" />
                  <input
                    value={locationInput}
                    onChange={(event) => {
                      const nextValue = event.target.value;
                      setLocationInput(nextValue);
                      setSelectedMarket(null);
                      setSuggestOpen(true);
                    }}
                        autoFocus
                    onKeyDown={(event) => { if (event.key === 'Enter') { applyLocation(); } }}
                    placeholder="Wpisz miasto, dzielnicę lub miejscowość"
                  />
                      {suggestLoading && <span className="map-suggest-spinner" aria-hidden="true" />}
                    </div>
                    <div className="history-location-suggest" role="listbox" aria-label="Podpowiedzi lokalizacji">
                      {shownSuggestions.length > 0 ? shownSuggestions.map((place) => (
                        <button type="button" key={`${place.id}-${place.label}-${place.sublabel}`} onClick={() => selectSuggestion(place)}>
                          <Icon name="pin" />
                          <span><strong>{place.label}</strong><small>{place.sublabel || 'Polska'}</small></span>
                        </button>
                      )) : <p>Brak wyników dla „{locationQuery}"</p>}
                    </div>
                </div>
                )}
              </div>
            </label>
            <label className="history-field">
              <span>Rodzaj nieruchomości</span>
              <select value={propertyType} onChange={(event) => setPropertyType(event.target.value as HistoryPropertyType)}><option>Mieszkanie</option><option>Dom</option><option>Lokal użytkowy</option></select>
            </label>
            <label className="history-field">
              <span>Rynek</span>
              <select value={marketType} onChange={(event) => setMarketType(event.target.value as HistoryMarketType)}><option>Wtórny i pierwotny</option><option>Wtórny</option><option>Pierwotny</option></select>
            </label>
            <label className="history-field">
              <span>Okres</span>
              <select value={period} onChange={(event) => setPeriod(event.target.value as HistoryPeriod)}>{HISTORY_PERIOD_OPTIONS.map((option) => <option value={option.value} key={option.value}>{option.label}</option>)}</select>
            </label>
            <button className="history-search" type="button" onClick={applyLocation}><Icon name="search" /> Pokaż historię</button>
          </div>
          <div className="history-popular-row">
            <span>Popularne lokalizacje:</span>
            {HISTORY_POPULAR_MARKETS.map((item) => <button type="button" key={item} onClick={() => applyMarket(resolveHistoryMarket(item))}>{item}</button>)}
            <button className="history-clear" type="button" onClick={clearHistoryFilters}>Wyczyść filtry</button>
          </div>
        </section>

        <div className="history-layout">
          <div className="history-main-column">
            <section className="history-chart-card">
              <div className="history-chart-head">
                <div>
                  <h2>Średnia cena za m² w {activeLocationLabel}</h2>
                  <strong>{formatM2(latestPrice)}</strong>
                  <p className={changePercent >= 0 ? 'positive' : 'negative'}>{changePercent >= 0 ? '+' : ''}{changePercent.toFixed(1).replace('.', ',')}% <span>({HISTORY_PERIOD_OPTIONS.find((option) => option.value === period)?.label.toLowerCase()})</span></p>
                  <small className="history-source-note">{HISTORY_2026_SOURCE_NOTE}</small>
                </div>
                <span className="history-chart-mode">Cena za m²</span>
              </div>

              <div
                className={`history-chart-wrap ${hasSelectedMarket ? '' : 'empty'}`}
                onMouseMove={handleHistoryChartMouseMove}
                onMouseLeave={() => setHoveredHistoryIndex(null)}
              >
                <svg
                  viewBox={`0 0 ${chartWidth} ${chartHeight}`}
                  role="img"
                  aria-label="Wykres historii cen"
                  focusable="false"
                >
                  <defs>
                    <linearGradient id="historyArea" x1="0" x2="0" y1="0" y2="1">
                      <stop offset="0" stopColor="#10a15e" stopOpacity="0.18" />
                      <stop offset="1" stopColor="#10a15e" stopOpacity="0" />
                    </linearGradient>
                  </defs>
                  {chartTicks.map((tick) => {
                    const range = chartAxisMax - chartAxisMin || 1;
                    const y = chartPaddingTop + (1 - (tick - chartAxisMin) / range) * chartInnerHeight;
                    const labelY = Math.max(13, Math.min(chartHeight - 10, y - 7));
                    return <g key={tick}><line x1="0" x2={chartWidth} y1={y} y2={y} /><text x="0" y={labelY}>{formatPln(tick)}</text></g>;
                  })}
                  <path className="history-area" d={areaPath} />
                  <path className="history-line" d={linePath} />
                  {pointCoords.map((point, index) => <circle className={`history-dot ${index === hoveredHistoryIndex ? 'active' : ''}`} cx={point.x} cy={point.y} r={index === hoveredHistoryIndex ? '6' : '4'} key={`${point.price}-${index}`} />)}
                  {hasSelectedMarket && hoveredPoint && <line className="history-guide" x1={hoveredPoint.x} x2={hoveredPoint.x} y1={hoveredPoint.y} y2={chartBaseY} />}
                  {hasSelectedMarket && hoveredPoint && (
                    <foreignObject x={tooltipX} y={tooltipY} width={tooltipWidth} height={tooltipHeight + 8}>
                      <div className="history-tooltip"><span>{hoveredDate}</span><strong>{formatM2(hoveredPoint.sourcePrice)}</strong></div>
                    </foreignObject>
                  )}
                </svg>
                {!hasSelectedMarket && <div className="history-chart-empty"><strong>0 zł/m²</strong><span>Wpisz lokalizację, żeby wczytać ceny 2026 dla miasta, mniejszej miejscowości albo wsi.</span></div>}
                <div className="history-months" style={{ gridTemplateColumns: `repeat(${monthLabels.length}, minmax(0, 1fr))` }}>{monthLabels.map((month, index) => <span key={`${month}-${index}`}>{month}</span>)}</div>
              </div>

              <div className="history-stat-grid">
                <article><span>Najniższa cena 2026 do dziś</span><strong>{formatM2(minPrice)}</strong><small>{monthLabels[minIndex] || 'w okresie'}</small></article>
                <article><span>Najwyższa cena 2026 do dziś</span><strong>{formatM2(maxPrice)}</strong><small>{monthLabels[maxIndex] || 'w okresie'}</small></article>
                <article className={changePercent >= 0 ? 'positive' : 'negative'}><span>Zmiana od stycznia</span><strong>{changePercent >= 0 ? '+' : ''}{changePercent.toFixed(1).replace('.', ',')}%</strong><small>{changeValue >= 0 ? '+' : ''}{formatM2(changeValue)}</small></article>
                <article><span>Średnia cena 2026 do dziś</span><strong>{formatM2(averagePrice)}</strong></article>
              </div>
            </section>

            <section className="history-insights-card">
              <h2>Wnioski z analizy</h2>
              <div>
                <article><span><Icon name="trend" /></span><p><strong>{trendLabel}</strong><small>{trendText}</small></p></article>
                <article><span><Icon name="chart" /></span><p><strong>Najniższy punkt wykresu</strong><small>Najkorzystniejszy poziom od stycznia do {todayLabel} to {formatM2(minPrice)} ({monthLabels[minIndex] || 'w okresie'}).</small></p></article>
                <article><span><Icon name="warning" /></span><p><strong>Warto obserwować</strong><small>{hasSelectedMarket ? `${propertyType} na rynku „${marketType}” ma aktualnie średnią ${formatM2(latestPrice)} w lokalizacji ${activeLocationLabel}.` : 'Po wyborze lokalizacji pokażemy średnią cenę za m², zmianę i punkty minimum/maksimum dla modelu 2026.'}</small></p></article>
              </div>
            </section>

            <section className="history-alert-card">
              <span><Icon name="bell" /></span>
              <p><strong>Chcesz być na bieżąco?</strong><small>Ustaw alert cenowy i otrzymuj powiadomienia o zmianach cen w wybranej lokalizacji.</small></p>
              <button type="button" onClick={createCityAlert}><Icon name="bell" /> {selectedMarketAlreadyAlerted ? 'Alert już aktywny' : 'Utwórz alert cenowy'}</button>
              {createAlertFeedback && <small className="history-alert-feedback">{createAlertFeedback}</small>}
            </section>
          </div>

          <aside className="history-sidebar">
            <section className="history-side-card compare">
              <h2>Porównaj lokalizacje</h2>
              {displayedComparisons.map((market, index) => (
                <button className={hasSelectedMarket && index === 0 ? 'active' : ''} key={market.key} type="button" onClick={() => applyMarket(market)}><span>{historyShortMarketLabel(market)}</span><strong>{formatM2(Math.round(market.apartmentM2 * HISTORY_PROPERTY_MULTIPLIERS[propertyType] * HISTORY_MARKET_MULTIPLIERS[marketType]))} <Icon name="trend" /></strong></button>
              ))}
              <button type="button">Porównaj więcej lokalizacji <Icon name="link" /></button>
            </section>

            <section className="history-side-card recent">
              <h2>Ostatnio przeglądane</h2>
              {recentMarkets.map((item, index) => (
                <article key={item.key} onClick={() => applyMarket(item)}>
                  <img src={index % 2 === 0 ? heroImage : cityImage} alt="" />
                  <p><strong>{item.label}</strong><span>{historyRecentMeta(propertyType)}</span><small><Icon name="trend" /> {formatM2(Math.round(item.apartmentM2 * HISTORY_PROPERTY_MULTIPLIERS[propertyType]))}</small></p>
                  <Icon name="chevron" />
                </article>
              ))}
              <button type="button">Zobacz całą historię <Icon name="link" /></button>
            </section>
          </aside>
        </div>
      </div>
    </section>
  );
}

function RealListingsBand({
  offerType,
  onOpenListing,
  city,
  isFavorite,
  onToggleFavorite,
}: {
  offerType: ApiOfferType;
  onOpenListing: (id: number) => void;
  city?: string;
  isFavorite: (listingId: number) => boolean;
  onToggleFavorite: (listing: ApiListingSummary) => void;
}) {
  const { items, loading } = useLiveListings(offerType);

  const cityFilter = (city ?? '').trim();
  const visible = cityFilter ? items.filter((item) => cityStartsWith(item.city, cityFilter)) : items;

  if (loading || visible.length === 0) {
    return null;
  }

  return (
    <div className="real-listings-section" aria-label="Ogłoszenia dodane w serwisie">
      <div className="real-listings-heading">
        <h3>Ogłoszenia dodane w serwisie</h3>
        <span>{visible.length}</span>
      </div>
      <div className="result-list">
        {visible.map((item) => {
          const favorite = isFavorite(item.id);
          const floor = listingFloorText(item.floor, item.buildingFloors);
          const meta = [
            `${`${item.area}`.replace('.', ',')} m²`,
            listingRoomsText(item.rooms),
            floor ? `${floor} piętro` : null,
            item.yearBuilt ? `${item.yearBuilt} r.` : null,
          ].filter(Boolean).join(' • ');
          return (
            // Karta prowadzi do stron oferty. Tytul jest linkiem rozciagnietym na cala
            // karte (::after), dzieki czemu mamy prawdziwy <a href> - do skopiowania i
            // otwarcia w nowej karcie - bez zagniezdzania przyciskow w <a>.
            <article key={item.id} className="result-card real-result-card">
              <div className="result-photo" style={{ backgroundImage: `url(${item.coverPhoto ?? heroImage})` }}>
                {offerType === 'SALE' && (
                  <div className="real-photo-badges">
                    <span className="green-tag">{listingMarketBadge(item.market)}</span>
                  </div>
                )}
                <button
                  type="button"
                  className={favorite ? 'real-favorite-button saved' : 'real-favorite-button'}
                  aria-label={favorite ? 'Usuń z ulubionych' : 'Dodaj do ulubionych'}
                  aria-pressed={favorite}
                  onClick={(event) => {
                    event.stopPropagation();
                    void onToggleFavorite(item);
                  }}
                >
                  <Icon name={favorite ? 'heart-filled' : 'heart'} />
                </button>
              </div>

              <div className="result-info">
                <h3><Link className="card-stretched-link" to={listingPath(item.id)}>{item.title}</Link></h3>
                <p>{[item.city, item.district].filter(Boolean).join(', ')}</p>
                <small>{meta}</small>
              </div>

              <div className="result-side">
                <strong>{formatPln(item.price)}{item.offerType === 'RENT' ? ' /mc' : ''}</strong>
                <span>{listingRelativeDate(item.createdAt)}</span>
                <Link className="real-card-cta" to={listingPath(item.id)}>
                  Zobacz ogłoszenie
                </Link>
              </div>
            </article>
          );
        })}
      </div>
    </div>
  );
}

function BuyPage({
  filters,
  onOpenMap,
  onOpenNegotiation,
  onOpenListing,
  isFavorite,
  onToggleFavorite,
  savedSearchActive,
  onToggleSavedSearch,
}: {
  filters: FilterState;
  onOpenMap: (city: string, mode?: 'buy' | 'rent') => void;
  onOpenNegotiation: (offer: BuyOffer) => void;
  onOpenListing: (id: number) => void;
  isFavorite: (listingId: number) => boolean;
  onToggleFavorite: (listing: ApiListingSummary) => void;
  savedSearchActive: boolean;
  onToggleSavedSearch: () => void;
}) {
  const { city, propertyType, pMin, pMax, aMin, aMax, rooms, wtorny, pierwotny, noFeeOnly, chips, resetAll } = filters;
  const [sort, setSort] = useState<SortKey>('default');
  const [sortOpen, setSortOpen] = useState(false);
  const sortRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!sortOpen) {
      return;
    }
    const handler = (event: MouseEvent) => {
      if (sortRef.current && !sortRef.current.contains(event.target as Node)) {
        setSortOpen(false);
      }
    };
    document.addEventListener('mousedown', handler);
    return () => document.removeEventListener('mousedown', handler);
  }, [sortOpen]);

  const cityFilter = city.trim();
  const { items: liveSaleListings } = useLiveListings('SALE');
  const saleMapCenter = useResolvedCityCenter(city, CITY_COORDS.Warszawa);
  const saleGeoMarkers = useMemo(() => {
    const filtered = cityFilter
      ? liveSaleListings.filter((item) => cityStartsWith(item.city, cityFilter))
      : liveSaleListings;
    const source = cityFilter ? filtered : liveSaleListings;
    return source.slice(0, 12).map((item, index) => {
      const point = item.lat != null && item.lng != null ? clampCenter({ lat: item.lat, lng: item.lng }) : listingLatLng(item, index);
      return {
        id: `sale-${item.id}`,
        label: `${Math.round(item.price / 1000)} tys.`,
        lat: point.lat,
        lng: point.lng,
      };
    });
  }, [cityFilter, liveSaleListings]);

  const results = buyListings
    .filter((offer) => {
      if (cityFilter && !cityStartsWith(offer.city, cityFilter)) {
        return false;
      }
      if (offer.propertyType !== propertyType) {
        return false;
      }
      if (pMin != null && offer.priceValue < pMin) {
        return false;
      }
      if (pMax != null && offer.priceValue > pMax) {
        return false;
      }
      if (aMin != null && offer.area < aMin) {
        return false;
      }
      if (aMax != null && offer.area > aMax) {
        return false;
      }
      if (rooms != null && (rooms === 5 ? offer.rooms < 5 : offer.rooms !== rooms)) {
        return false;
      }
      if (offer.market === 'wtorny' && !wtorny) {
        return false;
      }
      if (offer.market === 'pierwotny' && !pierwotny) {
        return false;
      }
      if (noFeeOnly && !offer.tags.includes('Bez prowizji')) {
        return false;
      }
      return true;
    })
    .sort((a, b) => {
      if (sort === 'cheap') {
        return a.priceValue - b.priceValue;
      }
      if (sort === 'expensive') {
        return b.priceValue - a.priceValue;
      }
      if (sort === 'small') {
        return a.area - b.area;
      }
      if (sort === 'big') {
        return b.area - a.area;
      }
      if (sort === 'old') {
        return b.dateOrder - a.dateOrder;
      }
      return a.dateOrder - b.dateOrder;
    });

  const quickActions = [
    () => setSort('cheap'),
    () => filters.setNoFeeOnly(true),
    () => setSort('new'),
  ];

  return (
    <section className="buy-page" aria-label="Kup mieszkanie">
      <div className="buy-container">
        <div className="breadcrumb">
          <span>Strona główna</span>
          <Icon name="chevron" />
          <strong>Kup</strong>
        </div>

        <div className="buy-hero">
          <div>
            <h1>Kup mieszkanie bez prowizji</h1>
            <p>Znajdź oferty prywatne, deweloperskie i okazje cenowe w całej Polsce.</p>
          </div>
        </div>

        <div className="buy-search-row">
          <MegaSearch filters={filters} onOpenMap={onOpenMap} />

          <button
            className={`save-search ${savedSearchActive ? 'saved' : ''}`}
            type="button"
            aria-pressed={savedSearchActive}
            onClick={onToggleSavedSearch}
          >
            <Icon name="bell" />
            <span>{savedSearchActive ? 'Zapisano wyszukiwanie' : 'Zapisz wyszukiwanie'}</span>
          </button>
        </div>

        <div className="quick-filter-row">
          {buyQuickFilters.map((filter, index) => (
            <button className="quick-filter" key={filter.title} type="button" onClick={quickActions[index]}>
              <Icon name={filter.icon} />
              <span>
                <strong>{filter.title}</strong>
                <small>{filter.text}</small>
              </span>
            </button>
          ))}
        </div>

        <section className="buy-results-zone" aria-label="Wyniki wyszukiwania">
          <div className="buy-layout buy-layout-results">
            <section className="results-column" aria-label="Lista mieszkań">
            <div className="results-head">
              <h2>Znaleźliśmy {results.length} {flatsWord(results.length)}</h2>
              {chips.map((chip) => (
                <button className="filter-chip" type="button" key={chip.label} onClick={chip.onRemove}>
                  {chip.label} ×
                </button>
              ))}
            </div>

            <RealListingsBand
              offerType="SALE"
              onOpenListing={onOpenListing}
              city={city}
              isFavorite={isFavorite}
              onToggleFavorite={onToggleFavorite}
            />

            {results.length === 0 ? (
              <div className="results-empty">
                <Icon name="search" />
                <strong>Brak ofert dla wybranych kryteriów</strong>
                <p>Spróbuj zmienić miasto, zwiększyć zakres ceny lub metrażu.</p>
                <button type="button" onClick={resetAll}>Wyczyść filtry</button>
              </div>
            ) : (
              <div className="result-list">
                {results.map((listing) => (
                  <article className="result-card" key={`${listing.title}-${listing.district}`}>
                    <div
                      className="result-photo"
                      style={{ backgroundImage: `url(${listing.image === 'city' ? cityImage : heroImage})`, backgroundPosition: listing.position }}
                    >
                      <button aria-label="Dodaj do ulubionych" type="button">
                        <Icon name="heart" />
                      </button>
                    </div>

                    <div className="result-info">
                      <div className="tag-row">
                        {listing.tags.map((tag) => (
                          <span className={tag === 'Okazja' ? 'hot-tag' : 'green-tag'} key={tag}>
                            {tag === 'Okazja' && <Icon name="flame" />}
                            {tag}
                          </span>
                        ))}
                      </div>
                      <h3>{listing.title}</h3>
                      <p>{listing.district}</p>
                      <small>{offerMeta(listing)}</small>
                      <div className="score-line">
                        <span>{listing.score}</span>
                      </div>
                      <strong className="saving">{listing.saving}</strong>
                    </div>

                    <div className="result-side">
                      <strong>{formatPln(listing.priceValue)}</strong>
                      <span>{listing.date}</span>
                      <button type="button" onClick={() => onOpenNegotiation(listing)}>Negocjuj cenę</button>
                    </div>
                  </article>
                ))}
              </div>
            )}
            </section>

            <aside className="map-column" aria-label="Mapa ofert">
            <div className="buy-toolbar">
              <div className="toolbar-actions">
                <div className={`sort-select ${sortOpen ? 'open' : ''}`} ref={sortRef}>
                  <span className="sort-label">Sortuj:</span>
                  <button type="button" className="sort-trigger" onClick={() => setSortOpen((open) => !open)}>
                    <span>{SORT_OPTIONS.find((option) => option.value === sort)?.label}</span>
                    <Icon name="chevron" />
                  </button>
                  {sortOpen && (
                    <div className="sort-menu">
                      {SORT_OPTIONS.map((option) => (
                        <button
                          type="button"
                          key={option.value}
                          className={option.value === sort ? 'active' : ''}
                          onClick={() => { setSort(option.value); setSortOpen(false); }}
                        >
                          <span>{option.label}</span>
                          {option.value === sort && <Icon name="check" />}
                        </button>
                      ))}
                    </div>
                  )}
                </div>
                <div className="view-toggle">
                  <button className="active" type="button">
                    <Icon name="list" />
                    Lista
                  </button>
                  <button type="button" onClick={() => onOpenMap(city, 'buy')}>
                    <Icon name="pin" />
                    Mapa
                  </button>
                </div>
              </div>
            </div>
            <div className="map-card">
              <SatelliteMap
                markers={mapMarkers}
                center={saleMapCenter}
                cityLabel={city || 'Warszawa'}
                geoMarkers={saleGeoMarkers}
                onMapClick={() => onOpenMap(city, 'buy')}
              />
            </div>

            <div className="map-ai-card">
              <div className="ai-label">
                <Icon name="spark" />
                <span>AI Dopasowanie</span>
              </div>
              <p>Powiedz nam czego szukasz, a AI znajdzie oferty idealnie dopasowane do Ciebie.</p>
              <button type="button">
                <Icon name="spark" />
                Dopasuj mi mieszkanie
              </button>
            </div>
            </aside>
          </div>
        </section>
      </div>
    </section>
  );
}

function DualRange({
  min,
  max,
  step,
  low,
  high,
  onChange,
}: {
  min: number;
  max: number;
  step: number;
  low: number;
  high: number;
  onChange: (low: number, high: number) => void;
}) {
  const pctLow = ((low - min) / (max - min)) * 100;
  const pctHigh = ((high - min) / (max - min)) * 100;

  return (
    <div className="dual-range">
      <span className="dual-track" />
      <span className="dual-fill" style={{ left: `${pctLow}%`, right: `${100 - pctHigh}%` }} />
      <input
        type="range"
        min={min}
        max={max}
        step={step}
        value={low}
        onChange={(event) => onChange(Math.min(Number(event.target.value), high - step), high)}
      />
      <input
        type="range"
        min={min}
        max={max}
        step={step}
        value={high}
        onChange={(event) => onChange(low, Math.max(Number(event.target.value), low + step))}
      />
    </div>
  );
}

function SatelliteMap({
  markers,
  center,
  cityLabel,
  centerMarkerLabel,
  initialZoom,
  showCityTitle,
  geoMarkers,
  onMapClick,
}: {
  markers: string[][];
  center?: LatLng;
  cityLabel?: string;
  centerMarkerLabel?: string;
  initialZoom?: number;
  showCityTitle?: boolean;
  geoMarkers?: { id: string; lat: number; lng: number; label: string }[];
  onMapClick?: () => void;
}) {
  const mapCenter = center ?? CITY_COORDS.Warszawa;
  const [currentZoom, setCurrentZoom] = useState(initialZoom ?? 13);
  const mapSize = 640;

  useEffect(() => {
    setCurrentZoom(initialZoom ?? 13);
  }, [initialZoom, mapCenter.lat, mapCenter.lng]);

  const tiles = useMemo(() => createSatelliteTiles({
    centerLat: mapCenter.lat,
    centerLng: mapCenter.lng,
    height: mapSize,
    width: mapSize,
    zoom: currentZoom,
  }), [currentZoom, mapCenter.lat, mapCenter.lng, mapSize]);

  const positionedGeoMarkers = useMemo(() => {
    if (!geoMarkers || geoMarkers.length === 0) {
      return [] as { id: string; label: string; left: number; top: number }[];
    }
    const worldCenter = projectToWorld(mapCenter.lat, mapCenter.lng, currentZoom);
    const tileCount = 2 ** currentZoom;
    const worldWidth = tileCount * 256;
    return geoMarkers
      .map((marker) => {
        const worldPoint = projectToWorld(marker.lat, marker.lng, currentZoom);
        let dx = worldPoint.x - worldCenter.x;
        if (dx > worldWidth / 2) {
          dx -= worldWidth;
        } else if (dx < -worldWidth / 2) {
          dx += worldWidth;
        }
        return {
          id: marker.id,
          label: marker.label,
          left: mapSize / 2 + dx,
          top: mapSize / 2 + (worldPoint.y - worldCenter.y),
        };
      })
      .filter((marker) => marker.left >= 0 && marker.left <= mapSize && marker.top >= 0 && marker.top <= mapSize);
  }, [geoMarkers, mapCenter.lat, mapCenter.lng, currentZoom, mapSize]);
  const shouldRenderStaticMarkers = geoMarkers == null;

  const changeZoom = (delta: number) => {
    setCurrentZoom((current) => Math.max(6, Math.min(19, current + delta)));
  };

  return (
    <div
      className={`satellite-map ${onMapClick ? 'satellite-map-clickable' : ''}`}
      onWheel={(event) => {
        event.preventDefault();
        changeZoom(event.deltaY < 0 ? 1 : -1);
      }}
      onClick={() => onMapClick?.()}
      role={onMapClick ? 'button' : undefined}
      tabIndex={onMapClick ? 0 : undefined}
      onKeyDown={(event) => {
        if (!onMapClick) {
          return;
        }
        if (event.key === 'Enter' || event.key === ' ') {
          event.preventDefault();
          onMapClick();
        }
      }}
      aria-label={onMapClick ? 'Otwórz lokalizację w mapach' : undefined}
    >
      <div className="satellite-tiles" aria-hidden="true">
        {tiles.map((tile) => (
          <img
            alt=""
            height="256"
            key={`${tile.z}-${tile.x}-${tile.y}`}
            loading="lazy"
            src={`https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/${tile.z}/${tile.y}/${tile.x}`}
            style={{ left: tile.left, top: tile.top }}
            width="256"
          />
        ))}
      </div>
      <div className="satellite-map-shade" />
      <button className="map-current">
        <span />
        Szukaj na bieżącej mapie
      </button>
      <button className="draw-area">Rysuj obszar</button>
      {showCityTitle !== false && <strong className="map-city">{cityLabel ?? 'Warszawa'}</strong>}
      {centerMarkerLabel && (
        <>
          <span className="map-center-pin" style={{ left: '50%', top: '52%' }} aria-hidden="true" />
          <span className="map-price map-price-center-label" style={{ left: '50%', top: '44%' }}>{centerMarkerLabel}</span>
        </>
      )}
      {positionedGeoMarkers.map((marker) => (
        <span className="map-price" style={{ left: `${marker.left}px`, top: `${marker.top}px` }} key={marker.id}>
          {marker.label}
        </span>
      ))}
      {shouldRenderStaticMarkers && markers.map(([label, left, top]) => (
        <span className="map-price" style={{ left, top }} key={`${label}-${left}-${top}`}>
          {label}
        </span>
      ))}
      <div className="map-zoom">
        <button
          type="button"
          onClick={(event) => {
            event.stopPropagation();
            changeZoom(1);
          }}
          aria-label="Przybliż mapę"
        >+</button>
        <button
          type="button"
          onClick={(event) => {
            event.stopPropagation();
            changeZoom(-1);
          }}
          aria-label="Oddal mapę"
        >−</button>
      </div>
      <a
        className="map-attribution"
        href="https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer"
        rel="noreferrer"
        target="_blank"
      >
        Imagery © Esri
      </a>
    </div>
  );
}

type MapOfferPreview = {
  listingId?: number;
  title: string;
  city: string;
  district: string;
  rooms: number;
  area: number;
  priceValue: number;
  market: 'wtorny' | 'pierwotny';
  tags: string[];
  image: 'hero' | 'city';
  imageUrl?: string | null;
  lat?: number | null;
  lng?: number | null;
  meta: string;
};

type MapMarker = { id: number; lat: number; lng: number; label: string; offer: MapOfferPreview; ariaLabel?: string };

const MAP_MIN_ZOOM = 6;
const MAP_MAX_ZOOM = 15;

function InteractiveMap({
  center,
  zoom,
  onChange,
  markers,
  selectedId,
  onSelect,
}: {
  center: LatLng;
  zoom: number;
  onChange: (center: LatLng, zoom: number) => void;
  markers: MapMarker[];
  selectedId: number | null;
  onSelect: (id: number | null) => void;
}) {
  const containerRef = useRef<HTMLDivElement>(null);
  const [size, setSize] = useState({ w: 960, h: 560 });
  const dragRef = useRef<{ startX: number; startY: number; originX: number; originY: number; moved: boolean } | null>(null);

  useEffect(() => {
    const node = containerRef.current;
    if (!node) {
      return;
    }
    const measure = () => setSize({ w: node.clientWidth, h: node.clientHeight });
    measure();
    const observer = new ResizeObserver(measure);
    observer.observe(node);
    return () => observer.disconnect();
  }, []);

  const worldCenter = projectToWorld(center.lat, center.lng, zoom);
  const originX = worldCenter.x - size.w / 2;
  const originY = worldCenter.y - size.h / 2;
  const tileCount = 2 ** zoom;
  const tileSize = 256;

  const startTileX = Math.floor(originX / tileSize);
  const endTileX = Math.floor((originX + size.w) / tileSize);
  const startTileY = Math.floor(originY / tileSize);
  const endTileY = Math.floor((originY + size.h) / tileSize);

  const tiles: { key: string; left: number; top: number; x: number; y: number }[] = [];
  for (let tx = startTileX; tx <= endTileX; tx += 1) {
    for (let ty = startTileY; ty <= endTileY; ty += 1) {
      if (ty < 0 || ty >= tileCount) {
        continue;
      }
      const wrappedX = ((tx % tileCount) + tileCount) % tileCount;
      tiles.push({
        key: `${zoom}-${tx}-${ty}`,
        left: tx * tileSize - originX,
        top: ty * tileSize - originY,
        x: wrappedX,
        y: ty,
      });
    }
  }

  const onPointerDown = (event: ReactPointerEvent<HTMLDivElement>) => {
    event.currentTarget.setPointerCapture(event.pointerId);
    dragRef.current = {
      startX: event.clientX,
      startY: event.clientY,
      originX: worldCenter.x,
      originY: worldCenter.y,
      moved: false,
    };
  };

  const onPointerMove = (event: ReactPointerEvent<HTMLDivElement>) => {
    const drag = dragRef.current;
    if (!drag) {
      return;
    }
    const dx = event.clientX - drag.startX;
    const dy = event.clientY - drag.startY;
    if (Math.abs(dx) + Math.abs(dy) > 3) {
      drag.moved = true;
    }
    const next = unprojectFromWorld(drag.originX - dx, drag.originY - dy, zoom);
    onChange(clampCenter(next), zoom);
  };

  const onPointerUp = (event: ReactPointerEvent<HTMLDivElement>) => {
    const drag = dragRef.current;
    dragRef.current = null;
    if (drag && !drag.moved) {
      onSelect(null);
    }
    if (event.currentTarget.hasPointerCapture(event.pointerId)) {
      event.currentTarget.releasePointerCapture(event.pointerId);
    }
  };

  const changeZoom = (delta: number) => {
    const next = Math.min(MAP_MAX_ZOOM, Math.max(MAP_MIN_ZOOM, zoom + delta));
    onChange(center, next);
  };

  const onWheel = (event: ReactWheelEvent<HTMLDivElement>) => {
    changeZoom(event.deltaY < 0 ? 1 : -1);
  };

  return (
    <div className="imap" ref={containerRef}>
      <div
        className="imap-surface"
        onPointerDown={onPointerDown}
        onPointerMove={onPointerMove}
        onPointerUp={onPointerUp}
        onPointerCancel={onPointerUp}
        onWheel={onWheel}
      >
        <div className="imap-tiles" aria-hidden="true">
          {tiles.map((tile) => (
            <img
              alt=""
              key={tile.key}
              draggable={false}
              height={tileSize}
              width={tileSize}
              loading="lazy"
              src={`https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/${zoom}/${tile.y}/${tile.x}`}
              style={{ left: tile.left, top: tile.top }}
            />
          ))}
        </div>

        <div className="imap-tiles imap-labels" aria-hidden="true">
          {tiles.map((tile) => (
            <img
              alt=""
              key={`label-${tile.key}`}
              draggable={false}
              height={tileSize}
              width={tileSize}
              loading="lazy"
              src={`https://server.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/${zoom}/${tile.y}/${tile.x}`}
              style={{ left: tile.left, top: tile.top }}
            />
          ))}
        </div>

        {markers.map((marker) => {
          const world = projectToWorld(marker.lat, marker.lng, zoom);
          const left = world.x - originX;
          const top = world.y - originY;
          if (left < -60 || left > size.w + 60 || top < -60 || top > size.h + 60) {
            return null;
          }
          return (
            <button
              type="button"
              key={marker.id}
              className={`imap-marker rank-${marker.id} ${selectedId === marker.id ? 'active' : ''}`}
              style={{ left, top }}
              aria-label={marker.ariaLabel ?? marker.label}
              title={marker.ariaLabel}
              onPointerDown={(event) => event.stopPropagation()}
              onClick={(event) => {
                event.stopPropagation();
                onSelect(marker.id);
                onChange(clampCenter({ lat: marker.lat, lng: marker.lng }), zoom);
              }}
            >
              {marker.label}
            </button>
          );
        })}
      </div>

      <div className="imap-zoom">
        <button type="button" aria-label="Przybliż" onClick={() => changeZoom(1)}>+</button>
        <button type="button" aria-label="Oddal" onClick={() => changeZoom(-1)}>−</button>
      </div>

      <a
        className="map-attribution"
        href="https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer"
        rel="noreferrer"
        target="_blank"
      >
        Imagery © Esri
      </a>
    </div>
  );
}

function LocationPickerMap({
  center,
  marker,
  zoom,
  label,
  onChange,
  onPick,
}: {
  center: LatLng;
  marker: LatLng;
  zoom: number;
  label: string;
  onChange: (center: LatLng, zoom: number) => void;
  onPick: (point: LatLng) => void;
}) {
  const containerRef = useRef<HTMLDivElement>(null);
  const [size, setSize] = useState({ w: 720, h: 278 });
  const [draftMarker, setDraftMarker] = useState<LatLng | null>(null);
  const mapDragRef = useRef<{ startX: number; startY: number; originX: number; originY: number; moved: boolean } | null>(null);
  const markerDragRef = useRef<{ moved: boolean } | null>(null);

  useEffect(() => {
    const node = containerRef.current;
    if (!node) {
      return;
    }
    const measure = () => setSize({ w: node.clientWidth, h: node.clientHeight });
    measure();
    const observer = new ResizeObserver(measure);
    observer.observe(node);
    return () => observer.disconnect();
  }, []);

  const worldCenter = projectToWorld(center.lat, center.lng, zoom);
  const originX = worldCenter.x - size.w / 2;
  const originY = worldCenter.y - size.h / 2;
  const tileSize = 256;
  const tileCount = 2 ** zoom;
  const startTileX = Math.floor(originX / tileSize);
  const endTileX = Math.floor((originX + size.w) / tileSize);
  const startTileY = Math.floor(originY / tileSize);
  const endTileY = Math.floor((originY + size.h) / tileSize);
  const tiles: { key: string; left: number; top: number; x: number; y: number }[] = [];

  for (let tx = startTileX; tx <= endTileX; tx += 1) {
    for (let ty = startTileY; ty <= endTileY; ty += 1) {
      if (ty < 0 || ty >= tileCount) {
        continue;
      }
      const wrappedX = ((tx % tileCount) + tileCount) % tileCount;
      tiles.push({
        key: `${zoom}-${tx}-${ty}`,
        left: tx * tileSize - originX,
        top: ty * tileSize - originY,
        x: wrappedX,
        y: ty,
      });
    }
  }

  const shownMarker = draftMarker ?? marker;
  const markerWorld = projectToWorld(shownMarker.lat, shownMarker.lng, zoom);
  const markerLeft = markerWorld.x - originX;
  const markerTop = markerWorld.y - originY;
  const changeZoom = (delta: number) => {
    const nextZoom = Math.min(19, Math.max(5, zoom + delta));
    onChange(center, nextZoom);
  };

  const pointFromEvent = (event: ReactPointerEvent<HTMLElement>) => {
    const node = containerRef.current;
    if (!node) {
      return marker;
    }
    const rect = node.getBoundingClientRect();
    const x = originX + event.clientX - rect.left;
    const y = originY + event.clientY - rect.top;
    return clampCenter(unprojectFromWorld(x, y, zoom));
  };

  const onMapPointerDown = (event: ReactPointerEvent<HTMLDivElement>) => {
    event.currentTarget.setPointerCapture(event.pointerId);
    mapDragRef.current = {
      startX: event.clientX,
      startY: event.clientY,
      originX: worldCenter.x,
      originY: worldCenter.y,
      moved: false,
    };
  };

  const onMapPointerMove = (event: ReactPointerEvent<HTMLDivElement>) => {
    const drag = mapDragRef.current;
    if (!drag) {
      return;
    }
    const dx = event.clientX - drag.startX;
    const dy = event.clientY - drag.startY;
    if (Math.abs(dx) + Math.abs(dy) > 4) {
      drag.moved = true;
    }
    const nextCenter = unprojectFromWorld(drag.originX - dx, drag.originY - dy, zoom);
    onChange(clampCenter(nextCenter), zoom);
  };

  const onMapPointerUp = (event: ReactPointerEvent<HTMLDivElement>) => {
    const drag = mapDragRef.current;
    mapDragRef.current = null;
    if (event.currentTarget.hasPointerCapture(event.pointerId)) {
      event.currentTarget.releasePointerCapture(event.pointerId);
    }
    if (drag && !drag.moved) {
      onPick(pointFromEvent(event));
    }
  };

  const onMarkerPointerDown = (event: ReactPointerEvent<HTMLDivElement>) => {
    event.stopPropagation();
    event.currentTarget.setPointerCapture(event.pointerId);
    markerDragRef.current = { moved: false };
    setDraftMarker(marker);
  };

  const onToolPointerDown = (event: ReactPointerEvent<HTMLDivElement>) => {
    event.stopPropagation();
    event.currentTarget.setPointerCapture(event.pointerId);
    markerDragRef.current = { moved: false };
    setDraftMarker(pointFromEvent(event));
  };

  const onMarkerPointerMove = (event: ReactPointerEvent<HTMLDivElement>) => {
    const drag = markerDragRef.current;
    if (!drag) {
      return;
    }
    drag.moved = true;
    setDraftMarker(pointFromEvent(event));
  };

  const onMarkerPointerUp = (event: ReactPointerEvent<HTMLDivElement>) => {
    const drag = markerDragRef.current;
    markerDragRef.current = null;
    if (event.currentTarget.hasPointerCapture(event.pointerId)) {
      event.currentTarget.releasePointerCapture(event.pointerId);
    }
    const point = pointFromEvent(event);
    setDraftMarker(null);
    if (drag) {
      onPick(point);
    }
  };

  const onWheel = (event: ReactWheelEvent<HTMLDivElement>) => {
    event.preventDefault();
    changeZoom(event.deltaY < 0 ? 1 : -1);
  };

  return (
    <div className="add-location-map real-location-map" ref={containerRef}>
      <div
        className="real-location-surface"
        onPointerDown={onMapPointerDown}
        onPointerMove={onMapPointerMove}
        onPointerUp={onMapPointerUp}
        onPointerCancel={onMapPointerUp}
        onWheel={onWheel}
      >
        <div className="real-location-tiles" aria-hidden="true">
          {tiles.map((tile) => (
            <img
              alt=""
              draggable={false}
              height={tileSize}
              key={tile.key}
              loading="lazy"
              src={`https://tile.openstreetmap.org/${zoom}/${tile.x}/${tile.y}.png`}
              style={{ left: tile.left, top: tile.top }}
              width={tileSize}
            />
          ))}
        </div>

        <div
          className="add-location-pin real-location-pin"
          style={{ left: markerLeft, top: markerTop }}
          onPointerDown={onMarkerPointerDown}
          onPointerMove={onMarkerPointerMove}
          onPointerUp={onMarkerPointerUp}
          onPointerCancel={onMarkerPointerUp}
        >
          <Icon name="pin" />
        </div>
        <span className="add-location-map-label real-location-label" style={{ left: markerLeft, top: markerTop - 36 }}>
          {label}
        </span>
      </div>

      <div
        className="real-location-pin-tool"
        onPointerDown={onToolPointerDown}
        onPointerMove={onMarkerPointerMove}
        onPointerUp={onMarkerPointerUp}
        onPointerCancel={onMarkerPointerUp}
        title="Przeciągnij pinezkę na adres"
      >
        <Icon name="pin" />
      </div>

      <div className="real-location-zoom">
        <button type="button" aria-label="Przybliż" onClick={() => changeZoom(1)}>+</button>
        <button type="button" aria-label="Oddal" onClick={() => changeZoom(-1)}>−</button>
      </div>
      <a className="real-location-attribution" href="https://www.openstreetmap.org/copyright" rel="noreferrer" target="_blank">
        © OpenStreetMap
      </a>
    </div>
  );
}

function MapSearchPage({ initialCity, offerType }: { initialCity: string; offerType: ApiOfferType }) {
  const navigate = useNavigate();
  const knownInitialCity = findKnownCityKey(initialCity);
  const startCity = knownInitialCity ?? '';
  const initialCityValue = initialCity.trim();
  const isRentMode = offerType === 'RENT';
  const { items: liveListings } = useLiveListings(offerType);
  const [cityInput, setCityInput] = useState(initialCity);
  const [city, setCity] = useState(initialCityValue);
  const [priceMin, setPriceMin] = useState('');
  const [priceMax, setPriceMax] = useState('');
  const [rooms, setRooms] = useState<number | null>(null);
  const [wtorny, setWtorny] = useState(true);
  const [pierwotny, setPierwotny] = useState(true);
  const [center, setCenter] = useState<LatLng>(startCity ? CITY_COORDS[startCity] : POLAND_CENTER);
  const [zoom, setZoom] = useState(startCity ? 12 : 6);
  const [selectedId, setSelectedId] = useState<number | null>(null);
  const [suggestions, setSuggestions] = useState<PlaceSuggestion[]>([]);
  const [suggestOpen, setSuggestOpen] = useState(false);
  const [suggestLoading, setSuggestLoading] = useState(false);
  const skipNextFetch = useRef(false);
  const autoCenteredRef = useRef(false);

  useEffect(() => {
    if (!initialCityValue || startCity) {
      return;
    }
    const controller = new AbortController();
    fetchPlaceSuggestions(initialCityValue, controller.signal)
      .then((places) => {
        if (places.length === 0) {
          return;
        }
        setCenter(clampCenter({ lat: places[0].lat, lng: places[0].lng }));
        setZoom(13);
      })
      .catch(() => undefined);
    return () => controller.abort();
  }, [initialCityValue, startCity]);

  useEffect(() => {
    if (autoCenteredRef.current || startCity || city.trim() || cityInput.trim() || liveListings.length === 0) {
      return;
    }

    const counts = new Map<string, number>();
    liveListings.forEach((listing) => {
      counts.set(listing.city, (counts.get(listing.city) ?? 0) + 1);
    });

    const topCity = Array.from(counts.entries())
      .sort((left, right) => right[1] - left[1])[0]?.[0] ?? '';
    const coords = topCity ? CITY_COORDS[topCity] : null;
    if (!coords) {
      return;
    }

    autoCenteredRef.current = true;
    skipNextFetch.current = true;
    setCityInput(topCity);
    setCity(topCity);
    setCenter(coords);
    setZoom(12);
    setSelectedId(null);
    setSuggestOpen(false);
    setSuggestions([]);
  }, [city, cityInput, liveListings, startCity]);

  useEffect(() => {
    const query = cityInput.trim();
    if (skipNextFetch.current) {
      skipNextFetch.current = false;
      return;
    }
    if (query.length < 2) {
      setSuggestions([]);
      setSuggestLoading(false);
      return;
    }
    const controller = new AbortController();
    setSuggestLoading(true);
    const timer = window.setTimeout(() => {
      fetchPlaceSuggestions(query, controller.signal)
        .then((places) => {
          setSuggestions(places);
          setSuggestOpen(true);
        })
        .catch((error) => {
          if ((error as Error).name !== 'AbortError') {
            setSuggestions([]);
          }
        })
        .finally(() => setSuggestLoading(false));
    }, 300);
    return () => {
      controller.abort();
      window.clearTimeout(timer);
    };
  }, [cityInput]);

  const pMin = parseNumber(priceMin);
  const pMax = parseNumber(priceMax);
  const cityFilter = city.trim();

  const fallbackOffers: MapOfferPreview[] = isRentMode
    ? rentListings.map((offer) => ({
      title: offer.title,
      city: offer.city,
      district: offer.district,
      rooms: offer.rooms,
      area: offer.area,
      priceValue: offer.priceValue,
      market: offer.market,
      tags: offer.tags,
      image: 'hero',
      meta: `${roomsLabel(offer.rooms)}  •  ${offer.area} m²  •  Najem`,
    }))
    : buyListings.map((offer) => ({
      title: offer.title,
      city: offer.city,
      district: offer.district,
      rooms: offer.rooms,
      area: offer.area,
      priceValue: offer.priceValue,
      market: offer.market,
      tags: offer.tags,
      image: offer.image,
      meta: offerMeta(offer),
    }));

  const sourceOffers = liveListings.length > 0
    ? liveListings.map((listing) => toMapOfferPreview(listing, listing))
    : fallbackOffers;

  const results = sourceOffers
    .map((offer, index) => ({ offer, index }))
    .filter(({ offer }) => {
      if (cityFilter && !cityStartsWith(offer.city, cityFilter)) {
        return false;
      }
      if (pMin != null && offer.priceValue < pMin) {
        return false;
      }
      if (pMax != null && offer.priceValue > pMax) {
        return false;
      }
      if (rooms != null && (rooms === 5 ? offer.rooms < 5 : offer.rooms !== rooms)) {
        return false;
      }
      if (offer.market === 'wtorny' && !wtorny) {
        return false;
      }
      if (offer.market === 'pierwotny' && !pierwotny) {
        return false;
      }
      return true;
    });

  const markers: MapMarker[] = results.map(({ offer, index }) => {
    const point = offer.lat != null && offer.lng != null
      ? clampCenter({ lat: offer.lat, lng: offer.lng })
      : listingLatLng(offer, index);
    return {
      id: index,
      lat: point.lat,
      lng: point.lng,
      label: isRentMode ? `${offer.priceValue} zł` : `${Math.round(offer.priceValue / 1000)} tys.`,
      offer,
    };
  });

  const selectedMarker = markers.find((marker) => marker.id === selectedId) ?? null;

  const goToCity = (name: string) => {
    const coords = CITY_COORDS[name];
    skipNextFetch.current = true;
    setCityInput(name);
    setCity(name);
    setSelectedId(null);
    setSuggestOpen(false);
    setSuggestions([]);
    if (coords) {
      setCenter(coords);
      setZoom(12);
    }
  };

  const goToPlace = (place: PlaceSuggestion) => {
    skipNextFetch.current = true;
    setCityInput(place.label);
    setCity(place.label);
    setSelectedId(null);
    setSuggestOpen(false);
    setSuggestions([]);
    setCenter({ lat: place.lat, lng: place.lng });
    setZoom(13);
  };

  const applySearch = () => {
    const trimmed = cityInput.trim();
    if (!trimmed) {
      setCity('');
      setSuggestOpen(false);
      setCenter(POLAND_CENTER);
      setZoom(6);
      return;
    }

    const matchedKnownCity = findKnownCityKey(trimmed);
    if (matchedKnownCity) {
      goToCity(matchedKnownCity);
      return;
    }

    if (suggestions.length > 0) {
      goToPlace(suggestions[0]);
      return;
    }
    const controller = new AbortController();
    setSuggestLoading(true);
    fetchPlaceSuggestions(trimmed, controller.signal)
      .then((places) => {
        if (places.length > 0) {
          goToPlace(places[0]);
        } else {
          setCity(trimmed);
        }
      })
      .catch(() => setCity(trimmed))
      .finally(() => setSuggestLoading(false));
  };

  const resetView = () => {
    setCity('');
    setCityInput('');
    setPriceMin('');
    setPriceMax('');
    setRooms(null);
    setWtorny(true);
    setPierwotny(true);
    setSelectedId(null);
    setSuggestOpen(false);
    setSuggestions([]);
    setCenter(POLAND_CENTER);
    setZoom(6);
  };

  return (
    <section className="map-search-page" aria-label="Mapa ofert">
      <div className="map-search-bar">
        <Link className="map-back" to={isRentMode ? ROUTES.rent : ROUTES.buy}>
          <Icon name="chevron" />
          <span>Wróć do listy</span>
        </Link>

        <div className="map-field map-field-city">
          <Icon name="search" />
          <input
            type="text"
            placeholder="Szukaj miasta, wsi lub miejscowości w Polsce"
            value={cityInput}
            onChange={(event) => { setCityInput(event.target.value); setSuggestOpen(true); }}
            onFocus={() => { if (suggestions.length > 0) { setSuggestOpen(true); } }}
            onBlur={() => window.setTimeout(() => setSuggestOpen(false), 150)}
            onKeyDown={(event) => { if (event.key === 'Enter') { applySearch(); } }}
          />
          {suggestLoading && <span className="map-suggest-spinner" aria-hidden="true" />}
          {suggestOpen && (suggestions.length > 0 || (!suggestLoading && cityInput.trim().length >= 2)) && (
            <div className="map-suggest">
              {suggestions.map((place) => (
                <button
                  type="button"
                  key={place.id}
                  onMouseDown={(event) => event.preventDefault()}
                  onClick={() => goToPlace(place)}
                >
                  <Icon name="pin" />
                  <span>
                    <strong>{place.label}</strong>
                    {place.sublabel && <small>{place.sublabel}</small>}
                  </span>
                </button>
              ))}
              {suggestions.length === 0 && !suggestLoading && (
                <p className="map-suggest-empty">Brak wyników dla „{cityInput.trim()}"</p>
              )}
            </div>
          )}
        </div>

        <div className="map-field map-field-num">
          <input inputMode="numeric" placeholder="Cena od" value={priceMin} onChange={(event) => setPriceMin(event.target.value)} />
        </div>
        <div className="map-field map-field-num">
          <input inputMode="numeric" placeholder="Cena do" value={priceMax} onChange={(event) => setPriceMax(event.target.value)} />
        </div>

        <div className="map-field map-field-select">
          <select value={rooms == null ? '' : String(rooms)} onChange={(event) => setRooms(event.target.value === '' ? null : Number(event.target.value))}>
            <option value="">Pokoje: dowolne</option>
            {ROOM_OPTIONS.map((value) => (
              <option key={value} value={value}>{value === 5 ? '5+ pokoi' : roomsLabel(value)}</option>
            ))}
          </select>
        </div>

        <div className="map-field map-field-select">
          <select
            value={wtorny && pierwotny ? 'both' : wtorny ? 'wtorny' : pierwotny ? 'pierwotny' : 'both'}
            onChange={(event) => {
              const value = event.target.value;
              setWtorny(value === 'both' || value === 'wtorny');
              setPierwotny(value === 'both' || value === 'pierwotny');
            }}
          >
            <option value="both">Rynek: dowolny</option>
            <option value="wtorny">Tylko wtórny</option>
            <option value="pierwotny">Tylko pierwotny</option>
          </select>
        </div>

        <button type="button" className="map-search-btn" onClick={applySearch}>
          <Icon name="search" />
          <span>Szukaj</span>
        </button>
        <button type="button" className="map-reset-btn" onClick={resetView}>
          Wyczyść
        </button>
      </div>

      <div className="map-city-chips">
        {POPULAR_CITIES.map((name) => (
          <button type="button" key={name} className={city === name ? 'active' : ''} onClick={() => goToCity(name)}>
            {name}
          </button>
        ))}
      </div>

      <div className="map-stage">
        <div className="map-count">
          <strong>{markers.length}</strong> {flatsWord(markers.length)} na mapie
        </div>

        <InteractiveMap
          center={center}
          zoom={zoom}
          onChange={(nextCenter, nextZoom) => { setCenter(nextCenter); setZoom(nextZoom); }}
          markers={markers}
          selectedId={selectedId}
          onSelect={setSelectedId}
        />

        {selectedMarker && (
          <article className="map-offer-card">
            <button type="button" className="map-offer-close" aria-label="Zamknij" onClick={() => setSelectedId(null)}>
              ×
            </button>
            <div className="map-offer-photo" style={{ backgroundImage: `url(${selectedMarker.offer.imageUrl ?? (selectedMarker.offer.image === 'hero' ? heroImage : cityImage)})` }} />
            <div className="map-offer-body">
              <strong>{formatPln(selectedMarker.offer.priceValue)}{isRentMode ? ' /mc' : ''}</strong>
              <h3>{selectedMarker.offer.title}</h3>
              <p>{selectedMarker.offer.district}</p>
              <small>{selectedMarker.offer.meta}</small>
              <div className="map-offer-tags">
                {selectedMarker.offer.tags.map((tag) => (
                  <span key={tag}>{tag}</span>
                ))}
              </div>
            </div>
          </article>
        )}
      </div>
    </section>
  );
}

function Header({
  onLogout,
  hasUnreadFavorites,
  onOpenNotification,
}: {
  onLogout: () => void;
  hasUnreadFavorites: boolean;
  onOpenNotification: (notification: DisplayNotification) => void;
}) {
  const navigate = useNavigate();
  const { user } = useAuth();
  const { notifications: serverNotifications, unreadCount } = useNotifications();
  const isLoggedIn = Boolean(user);
  const isAdmin = user?.role === 'ADMIN';
  const [isNotificationsOpen, setIsNotificationsOpen] = useState(false);
  const notificationPopoverRef = useRef<HTMLDivElement | null>(null);
  const [isAccountMenuOpen, setIsAccountMenuOpen] = useState(false);
  const accountMenuRef = useRef<HTMLDivElement | null>(null);

  // Dane powiadomień z centralnego providera. W dzwonku pokazujemy nieprzeczytane (znikają po kliknięciu).
  const displayNotifications = toDisplayNotifications(serverNotifications);
  const previewNotifications = displayNotifications.filter((item) => item.unread).slice(0, 6);

  useEffect(() => {
    if (!isNotificationsOpen) {
      return;
    }

    const onPointerDown = (event: MouseEvent) => {
      if (!notificationPopoverRef.current) {
        return;
      }
      if (!notificationPopoverRef.current.contains(event.target as Node)) {
        setIsNotificationsOpen(false);
      }
    };

    const onEscape = (event: KeyboardEvent) => {
      if (event.key === 'Escape') {
        setIsNotificationsOpen(false);
      }
    };

    window.addEventListener('pointerdown', onPointerDown);
    window.addEventListener('keydown', onEscape);
    return () => {
      window.removeEventListener('pointerdown', onPointerDown);
      window.removeEventListener('keydown', onEscape);
    };
  }, [isNotificationsOpen]);

  useEffect(() => {
    if (!isAccountMenuOpen) {
      return;
    }

    const onPointerDown = (event: MouseEvent) => {
      if (!accountMenuRef.current) {
        return;
      }
      if (!accountMenuRef.current.contains(event.target as Node)) {
        setIsAccountMenuOpen(false);
      }
    };

    const onEscape = (event: KeyboardEvent) => {
      if (event.key === 'Escape') {
        setIsAccountMenuOpen(false);
      }
    };

    window.addEventListener('pointerdown', onPointerDown);
    window.addEventListener('keydown', onEscape);
    return () => {
      window.removeEventListener('pointerdown', onPointerDown);
      window.removeEventListener('keydown', onEscape);
    };
  }, [isAccountMenuOpen]);

  // Pozycje menu sa linkami; zamkniecie popovera to efekt uboczny kliknięcia, nie nawigacja.
  const closeAccountMenu = () => setIsAccountMenuOpen(false);

  return (
    <header className={isAdmin ? 'header header-admin' : 'header'}>
      <div className="brand-group">
        <Link className="brand" to={ROUTES.home} aria-label="Mieszko">
          <span className="brand-mark">
            <Icon name="logo" />
          </span>
          <span>
            <strong>mieszko</strong>
            <small>mieszkania za darmo</small>
          </span>
        </Link>

        {isAdmin && <span className="brand-admin-badge">ADMIN</span>}
      </div>

      <nav className="nav" aria-label="Główna nawigacja">
        <NavLink to={ROUTES.buy} className={navLinkClass}>
          Kupuję
        </NavLink>
        <NavLink to={ROUTES.rent} className={navLinkClass}>
          Wynajmuję
        </NavLink>
        <NavLink to={ROUTES.sell} className={navLinkClass}>
          Jak sprzedawać
        </NavLink>
        <NavLink to={ROUTES.valuation} className={navLinkClass}>
          Wycena mieszkania
        </NavLink>
        <NavLink to={ROUTES.services} className={navLinkClass}>
          Firmy i usługi
        </NavLink>
        <NavLink to={ROUTES.guides} className={navLinkClass}>
          Poradniki
        </NavLink>
      </nav>

      <div className="header-actions">
        <NavLink
          to={ROUTES.favorites}
          className={({ isActive }) => (isActive ? 'icon-text favorites-shortcut active' : 'icon-text favorites-shortcut')}
        >
          <Icon name="heart" />
          <span>Ulubione</span>
          {hasUnreadFavorites && <i aria-hidden="true" />}
        </NavLink>
        {isLoggedIn && (
          <div className="notify-bell-wrap" ref={notificationPopoverRef}>
            <button
              className={isNotificationsOpen ? 'icon-text notify-bell open' : 'icon-text notify-bell'}
              type="button"
              aria-label="Powiadomienia"
              aria-expanded={isNotificationsOpen}
              aria-haspopup="dialog"
              onClick={() => setIsNotificationsOpen((current) => !current)}
            >
              <Icon name="bell" />
              {unreadCount > 0 && <i />}
            </button>

            {isNotificationsOpen && (
              <section className="notification-popover" aria-label="Powiadomienia">
                <div className="notification-popover-head">
                  <h3>Powiadomienia</h3>
                  <Link to={ROUTES.notifications} onClick={() => setIsNotificationsOpen(false)}>
                    Zobacz wszystkie
                  </Link>
                </div>

                <div className="notification-popover-list">
                  {previewNotifications.length === 0 && (
                    <p className="notification-popover-empty">Brak nowych powiadomień.</p>
                  )}
                  {previewNotifications.map((item) => (
                    <article
                      key={item.id}
                      className="notification-popover-item"
                      role="button"
                      tabIndex={0}
                      onClick={() => { setIsNotificationsOpen(false); onOpenNotification(item); }}
                    >
                      <span className={`notification-icon ${item.category}`}>
                        <Icon name={item.icon} />
                      </span>
                      <div>
                        <strong>{item.title}</strong>
                        <small>{item.description}</small>
                      </div>
                      <em>{item.timeLabel}</em>
                      {item.unread && <i className="notification-unread-dot" aria-hidden="true" />}
                    </article>
                  ))}
                </div>
              </section>
            )}
          </div>
        )}
        {isAdmin && (
          <NavLink
            to={ROUTES.admin}
            className={({ isActive }) => (isActive ? 'icon-text active' : 'icon-text')}
          >
            <Icon name="shield" />
            <span>Panel admina</span>
          </NavLink>
        )}
        {!isLoggedIn && (
          <Link className="icon-text" to={ROUTES.login}>
            <Icon name="user" />
            <span>Moje konto</span>
          </Link>
        )}
        {isLoggedIn && (
          <div className="account-menu-wrap" ref={accountMenuRef}>
            <button
              className={isAccountMenuOpen ? 'icon-text account-menu-trigger open' : 'icon-text account-menu-trigger'}
              type="button"
              aria-haspopup="menu"
              aria-expanded={isAccountMenuOpen}
              onClick={() => setIsAccountMenuOpen((current) => !current)}
            >
              <Icon name="user" />
              <span>{user?.fullName || user?.email}</span>
              <Icon name="chevron" />
            </button>

            {isAccountMenuOpen && (
              <div className="account-popover" role="menu" aria-label="Menu konta">
                <Link role="menuitem" to={ROUTES.account} onClick={closeAccountMenu}>
                  <Icon name="house" /> Panel główny
                </Link>
                <Link role="menuitem" to={ROUTES.accountListings} onClick={closeAccountMenu}>
                  <Icon name="document" /> Moje oferty
                </Link>
                <Link role="menuitem" to={ROUTES.accountSettings} onClick={closeAccountMenu}>
                  <Icon name="gear" /> Dane i ustawienia
                </Link>
                <button
                  type="button"
                  role="menuitem"
                  className="account-popover-logout"
                  onClick={() => {
                    setIsAccountMenuOpen(false);
                    onLogout();
                  }}
                >
                  <Icon name="lock" /> Wyloguj
                </button>
              </div>
            )}
          </div>
        )}
        <Link className="post-button" to={ROUTES.add}>
          <span>Dodaj ogłoszenie za darmo</span>
          <small>0 zł • Bez prowizji</small>
        </Link>
      </div>
    </header>
  );
}

function NotificationsPage({ onOpenNotification }: { onOpenNotification: (notification: DisplayNotification) => void }) {
  const [activeTab, setActiveTab] = useState<'all' | NotificationCategory>('all');
  const { notifications: serverNotifications } = useNotifications();

  // Cała historia z backendu (przeczytane pozostają na liście); mapowanie do kształtu widoku bez zmiany designu.
  const allNotifications = toDisplayNotifications(serverNotifications);
  const filteredNotifications = allNotifications.filter((item) => activeTab === 'all' || item.category === activeTab);
  const groupedNotifications = filteredNotifications.reduce<Record<'Dzisiaj' | 'Wczoraj', DisplayNotification[]>>((groups, item) => {
    groups[item.dayLabel].push(item);
    return groups;
  }, { Dzisiaj: [], Wczoraj: [] });

  return (
    <section className="notifications-page" aria-label="Powiadomienia użytkownika">
      <div className="notifications-shell">
        <div className="notifications-panel">
          <div className="notifications-head">
            <h1>Powiadomienia</h1>
            <button type="button" aria-label="Ustawienia powiadomień">
              <Icon name="gear" />
            </button>
          </div>

          <div className="notifications-tabs" role="tablist" aria-label="Filtr powiadomień">
            <button type="button" role="tab" aria-selected={activeTab === 'all'} className={activeTab === 'all' ? 'active' : ''} onClick={() => setActiveTab('all')}>Wszystkie</button>
            <button type="button" role="tab" aria-selected={activeTab === 'listing'} className={activeTab === 'listing' ? 'active' : ''} onClick={() => setActiveTab('listing')}>Ogłoszenia</button>
            <button type="button" role="tab" aria-selected={activeTab === 'messages'} className={activeTab === 'messages' ? 'active' : ''} onClick={() => setActiveTab('messages')}>Wiadomości</button>
            <button type="button" role="tab" aria-selected={activeTab === 'system'} className={activeTab === 'system' ? 'active' : ''} onClick={() => setActiveTab('system')}>System</button>
          </div>

          {(['Dzisiaj', 'Wczoraj'] as const).map((groupName) => (
            groupedNotifications[groupName].length > 0 ? (
              <div className="notifications-group" key={groupName}>
                <h2>{groupName}</h2>
                <div className="notifications-list">
                  {groupedNotifications[groupName].map((item) => (
                    <article
                      key={item.id}
                      className={`notification-row${item.unread ? ' unread' : ''}`}
                      role="button"
                      tabIndex={0}
                      onClick={() => onOpenNotification(item)}
                    >
                      <span className={`notification-icon ${item.category}`}>
                        <Icon name={item.icon} />
                      </span>
                      <div className="notification-copy">
                        <strong>{item.title}</strong>
                        <p>{item.description}</p>
                      </div>
                      <div className="notification-meta">
                        <small>{item.timeLabel}</small>
                        {item.unread && <i className="notification-unread-dot" aria-hidden="true" />}
                        {item.action === 'arrow' && <Icon name="arrow" />}
                      </div>
                    </article>
                  ))}
                </div>
              </div>
            ) : null
          ))}

        </div>
      </div>
    </section>
  );
}

function MessagesPage({
  onOpenListing,
}: {
  onOpenListing: (id: number) => void;
}) {
  const navigate = useNavigate();
  const location = useLocation();
  // Rozmowa otwarta z ogloszenia lub ulubionych przychodzi w location.state.
  // Po zalozeniu watku czyscimy state, zeby powrot Wstecz nie tworzyl go ponownie.
  const initialThreadTarget = (location.state as { threadTarget?: MessageThreadTarget } | null)?.threadTarget ?? null;
  const onThreadTargetHandled = useCallback(() => {
    navigate(location.pathname, { replace: true, state: null });
  }, [navigate, location.pathname]);
  type BusinessCard = {
    fullName: string;
    phone: string;
    email: string;
  };

  type ChatMessage = {
    id: string;
    text: string;
    time: string;
    mine: boolean;
    sentAt: number;
    readByRecipient?: boolean;
    unread?: boolean;
    attachmentName?: string;
    attachmentModeration?: 'pending';
    businessCard?: BusinessCard;
  };

  type ChatThread = {
    id: string;
    name: string;
    avatarImage: string;
    status: string;
    listing: MessageThreadListing | null;
    messages: ChatMessage[];
  };

  type ThreadFilter = 'all' | 'unread' | 'read';
  type ReportStep = 1 | 2 | 3;

  const reportReasons = [
    { id: 'fraud', icon: 'warning', title: 'Podejrzane lub oszukańcze działania', description: 'Użytkownik może próbować oszukać lub wyłudzić pieniądze.' },
    { id: 'inappropriate', icon: 'message', title: 'Nieodpowiednie treści', description: 'Ogłoszenia lub wiadomości zawierają treści obraźliwe lub nieodpowiednie.' },
    { id: 'harassment', icon: 'user', title: 'Nękanie lub zastraszanie', description: 'Użytkownik wysyła obraźliwe wiadomości lub nęka innych.' },
    { id: 'spam', icon: 'document', title: 'Spam lub duplikaty', description: 'Użytkownik publikuje wiele identycznych ogłoszeń lub spamuje.' },
    { id: 'other', icon: 'dots', title: 'Inny powód', description: 'Coś innego, o czym naszym zdaniem powinniśmy wiedzieć.' },
  ] as const;

  const seedNow = Date.now();
  const makeMessage = (
    id: string,
    text: string,
    time: string,
    mine: boolean,
    minutesAgo: number,
    extra?: { readByRecipient?: boolean; unread?: boolean },
  ): ChatMessage => ({
    id,
    text,
    time,
    mine,
    sentAt: seedNow - (minutesAgo * 60 * 1000),
    ...extra,
  });

  const initialThreads: ChatThread[] = [
    {
      id: 'anna',
      name: 'Anna Nowak',
      avatarImage: loginRoomImage,
      status: 'Ostatnio aktywna dziś o 11:20',
      listing: {
        listingId: 1,
        title: 'Mieszkanie 3-pokojowe',
        price: 748000,
        city: 'Warszawa',
        district: 'Mokotów, Stegny',
        area: 62,
        rooms: 3,
        floor: '4',
        buildingFloors: '8',
        thumbnail: heroImage,
      },
      messages: [
        makeMessage('anna-1', 'Dzień dobry, czy mieszkanie jest nadal dostępne?', '11:20', false, 34, { unread: true }),
        makeMessage('anna-2', 'Dzień dobry! Tak, mieszkanie jest nadal dostępne.', '11:21', true, 33, { readByRecipient: true }),
        makeMessage('anna-3', 'Świetnie! Czy moglibyśmy umówić się na prezentację w tym tygodniu?', '11:22', false, 32, { unread: true }),
        makeMessage('anna-4', 'Oczywiście, jestem dostępny w czwartek po 17:00 albo w piątek po 16:00. Która opcja Panią interesuje?', '11:23', true, 31, { readByRecipient: true }),
        makeMessage('anna-5', 'Czwartek po 17:00 będzie idealnie. Do zobaczenia!', '11:24', false, 30),
      ],
    },
    {
      id: 'piotr',
      name: 'Piotr Kowalski',
      avatarImage: cityImage,
      status: 'Ostatnio aktywny wczoraj',
      listing: null,
      messages: [
        makeMessage('piotr-1', 'Dziękuję za odpowiedź! Chętnie umówię się na prezentację.', 'Wczoraj', false, 250, { unread: true }),
        makeMessage('piotr-2', 'Mam wolny termin w środę po 18:00.', 'Wczoraj', true, 249, { readByRecipient: false }),
        makeMessage('piotr-3', 'Czy ten termin jest nadal aktualny?', '11:31', false, 28, { unread: true }),
      ],
    },
    {
      id: 'marta',
      name: 'Marta Wiśniewska',
      avatarImage: heroImage,
      status: 'Ostatnio aktywna 22.05',
      listing: null,
      messages: [
        makeMessage('marta-1', 'Czy istnieje możliwość negocjacji ceny?', '22.05', false, 700),
        makeMessage('marta-2', 'Tak, możemy rozmawiać o cenie po obejrzeniu mieszkania.', '22.05', true, 699, { readByRecipient: true }),
        makeMessage('marta-3', 'Rozumiem, dziękuję. Czy miejsce parkingowe jest w cenie?', '22.05', false, 698),
        makeMessage('marta-4', 'Dopytam jeszcze: czy jest komórka lokatorska?', '10:58', false, 61, { unread: true }),
      ],
    },
    {
      id: 'kamil',
      name: 'Kamil Zieliński',
      avatarImage: cityImage,
      status: 'Ostatnio aktywny 21.05',
      listing: null,
      messages: [
        makeMessage('kamil-1', 'Super, dziękuję. Do usłyszenia!', '21.05', false, 940),
        makeMessage('kamil-2', 'W razie pytań jestem dostępny od 9:00 do 18:00.', '21.05', true, 939, { readByRecipient: false }),
        makeMessage('kamil-3', 'Cześć, czy mogę zadzwonić dziś po 17:00?', '09:44', false, 135, { unread: true }),
      ],
    },
    {
      id: 'joanna',
      name: 'Joanna Wójcik',
      avatarImage: loginRoomImage,
      status: 'Ostatnio aktywna 20.05',
      listing: null,
      messages: [
        makeMessage('joanna-1', 'Wysłałaś zdjęcie', '20.05', false, 1180),
        makeMessage('joanna-2', 'Dziękuję, zdjęcia są bardzo pomocne.', '20.05', true, 1179, { readByRecipient: true }),
        makeMessage('joanna-3', 'Czy mogę prosić jeszcze o rzut mieszkania?', '20.05', false, 1178, { unread: true }),
      ],
    },
    {
      id: 'tomasz',
      name: 'Tomasz Lewandowski',
      avatarImage: heroImage,
      status: 'Ostatnio aktywny 19.05',
      listing: null,
      messages: [
        makeMessage('tomasz-1', 'Dzień dobry, interesuje mnie to mieszkanie.', '19.05', false, 1390),
      ],
    },
    {
      id: 'paulina',
      name: 'Paulina Kaczmarek',
      avatarImage: loginRoomImage,
      status: 'Ostatnio aktywna 18.05',
      listing: null,
      messages: [
        makeMessage('paulina-1', 'Dziękuję za informacje.', '18.05', false, 1650),
        makeMessage('paulina-2', 'Nie ma sprawy. Proszę dać znać, jeśli będzie Pani zainteresowana spotkaniem.', '18.05', true, 1649, { readByRecipient: true }),
        makeMessage('paulina-3', 'Jasne, odezwę się jutro rano.', '18.05', false, 1648),
      ],
    },
  ];

  const [query, setQuery] = useState('');
  const [activeFilter, setActiveFilter] = useState<ThreadFilter>('all');
  const [isFiltersOpen, setIsFiltersOpen] = useState(false);
  const [composerValue, setComposerValue] = useState('');
  const [selectedAttachment, setSelectedAttachment] = useState<File | null>(null);
  const [moderationFeedback, setModerationFeedback] = useState('');
  const [isBlockModalOpen, setIsBlockModalOpen] = useState(false);
  const [isDeleteChatModalOpen, setIsDeleteChatModalOpen] = useState(false);
  const [blockedThreadIds, setBlockedThreadIds] = useState<string[]>([]);
  const [isReportModalOpen, setIsReportModalOpen] = useState(false);
  const [reportStep, setReportStep] = useState<ReportStep>(1);
  const [selectedReportReason, setSelectedReportReason] = useState<string>(reportReasons[0].id);
  const [reportDetails, setReportDetails] = useState('');
  const [reportAttachments, setReportAttachments] = useState<File[]>([]);
  const [reportAttachmentError, setReportAttachmentError] = useState('');
  const [reportCaseNumber, setReportCaseNumber] = useState('');
  const [reportSubmittedAt, setReportSubmittedAt] = useState('');
  const [reportSubmittedReasonTitle, setReportSubmittedReasonTitle] = useState('');
  const [isSubmittingReport, setIsSubmittingReport] = useState(false);
  const [reportSubmitError, setReportSubmitError] = useState('');
  const [threads, setThreads] = useState<ChatThread[]>(initialThreads);
  const [activeThreadId, setActiveThreadId] = useState('anna');
  const attachmentInputRef = useRef<HTMLInputElement | null>(null);
  const reportAttachmentInputRef = useRef<HTMLInputElement | null>(null);
  const { user } = useAuth();
  const ADMIN_THREAD_ID = 'admin-support';

  useEffect(() => {
    if (!user || user.role === 'ADMIN') {
      return;
    }

    let cancelled = false;

    const loadAdminThread = () => {
      apiFetch<{ id: number; senderId: number; recipientId: number; content: string; createdAt: string; mine: boolean }[]>('/messages/admin')
        .then((data) => {
          if (cancelled) {
            return;
          }

          const messages: ChatMessage[] = data.map((item) => ({
            id: `admin-msg-${item.id}`,
            text: item.content,
            time: new Date(item.createdAt).toLocaleString('pl-PL', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }),
            mine: item.mine,
            sentAt: new Date(item.createdAt).getTime(),
          }));

          setThreads((current) => {
            const existingAdminThread = current.find((thread) => thread.id === ADMIN_THREAD_ID);
            const adminThread: ChatThread = {
              id: ADMIN_THREAD_ID,
              name: 'Administracja Mieszko',
              avatarImage: heroImage,
              status: 'Oficjalny kontakt z zespołem Mieszko',
              listing: existingAdminThread?.listing ?? null,
              messages,
            };
            if (!existingAdminThread) {
              return [adminThread, ...current];
            }
            return current.map((thread) => (thread.id === ADMIN_THREAD_ID ? adminThread : thread));
          });
        })
        .catch(() => {
          // Brak polaczenia z API - watek administracji zostaje pominiety/nieodswiezony.
        });
    };

    loadAdminThread();
    const intervalId = window.setInterval(loadAdminThread, 5000);

    return () => {
      cancelled = true;
      window.clearInterval(intervalId);
    };
  }, [user]);

  const threadItems = useMemo(() => threads.map((thread) => {
    const lastMessage = thread.messages[thread.messages.length - 1];
    const unreadCount = thread.messages.filter((message) => !message.mine && message.unread).length;
    const waitingReadCount = thread.messages.filter((message) => message.mine && message.readByRecipient === false).length;

    return {
      ...thread,
      preview: lastMessage?.text ?? '',
      time: lastMessage?.time ?? '',
      lastActivityAt: lastMessage?.sentAt ?? 0,
      unreadCount,
      waitingReadCount,
    };
  }), [threads]);

  const filteredThreads = threadItems.filter((thread) => {
    const value = `${thread.name} ${thread.preview}`.toLowerCase();
    const queryMatch = value.includes(query.trim().toLowerCase());
    if (!queryMatch) {
      return false;
    }
    if (activeFilter === 'unread') {
      return thread.unreadCount > 0;
    }
    if (activeFilter === 'read') {
      return thread.unreadCount === 0;
    }
    return true;
  }).sort((left, right) => right.lastActivityAt - left.lastActivityAt);

  const unreadThreadsCount = threadItems.filter((thread) => thread.unreadCount > 0).length;
  const readThreadsCount = threadItems.length - unreadThreadsCount;

  const activeThread = filteredThreads.find((thread) => thread.id === activeThreadId)
    ?? threadItems.find((thread) => thread.id === activeThreadId)
    ?? threadItems[0];

  const activeThreadMessages = threads.find((thread) => thread.id === activeThread.id)?.messages ?? [];
  const activeThreadListing = activeThread.listing;
  const activeThreadListingFloor = activeThreadListing ? listingFloorText(activeThreadListing.floor, activeThreadListing.buildingFloors) : null;
  const activeThreadListingLocation = activeThreadListing
    ? [activeThreadListing.city, activeThreadListing.district].filter(Boolean).join(', ')
    : '';
  const activeThreadListingSpecs = activeThreadListing
    ? `${activeThreadListing.area} m² • ${listingRoomsText(activeThreadListing.rooms)}${activeThreadListingFloor ? ` • ${activeThreadListingFloor} piętro` : ''}`
    : '';
  const isActiveThreadBlocked = blockedThreadIds.includes(activeThread.id);
  const selectedReportReasonDetails = reportReasons.find((reason) => reason.id === selectedReportReason);
  const isOtherReasonSelected = selectedReportReason === 'other';
  const normalizedReportDetails = reportDetails.trim();
  const needsMoreDetails = isOtherReasonSelected && normalizedReportDetails.length < 10;
  const canSubmitReport = !isSubmittingReport && !reportAttachmentError && !needsMoreDetails;

  useEffect(() => {
    if (filteredThreads.length === 0) {
      return;
    }

    const exists = filteredThreads.some((thread) => thread.id === activeThreadId);
    if (!exists) {
      setActiveThreadId(filteredThreads[0].id);
    }
  }, [activeThreadId, filteredThreads]);

  useEffect(() => {
    if (!moderationFeedback) {
      return;
    }

    const timeoutId = window.setTimeout(() => {
      setModerationFeedback('');
    }, 5000);

    return () => {
      window.clearTimeout(timeoutId);
    };
  }, [moderationFeedback]);

  useEffect(() => {
    if (!initialThreadTarget) {
      return;
    }

    setThreads((current) => {
      const exists = current.some((thread) => thread.id === initialThreadTarget.id);
      if (exists) {
        return current.map((thread) => (
          thread.id === initialThreadTarget.id
            ? {
                ...thread,
                name: initialThreadTarget.name,
                status: initialThreadTarget.status,
                avatarImage: initialThreadTarget.listing.thumbnail || thread.avatarImage,
                listing: initialThreadTarget.listing,
              }
            : thread
        ));
      }

      return [
        {
          id: initialThreadTarget.id,
          name: initialThreadTarget.name,
          avatarImage: initialThreadTarget.listing.thumbnail || cityImage,
          status: initialThreadTarget.status,
          listing: initialThreadTarget.listing,
          messages: [],
        },
        ...current,
      ];
    });

    setActiveFilter('all');
    setActiveThreadId(initialThreadTarget.id);
    onThreadTargetHandled();
  }, [initialThreadTarget, onThreadTargetHandled]);

  const markIncomingAsRead = (threadId: string) => {
    setThreads((current) => current.map((thread) => {
      if (thread.id !== threadId) {
        return thread;
      }

      const hasUnreadIncoming = thread.messages.some((message) => !message.mine && message.unread);
      if (!hasUnreadIncoming) {
        return thread;
      }

      return {
        ...thread,
        messages: thread.messages.map((message) => (
          message.mine || !message.unread ? message : { ...message, unread: false }
        )),
      };
    }));
  };

  const markOutgoingAsRead = (threadId: string) => {
    setThreads((current) => current.map((thread) => {
      if (thread.id !== threadId) {
        return thread;
      }

      const hasOutgoingPending = thread.messages.some((message) => message.mine && message.readByRecipient === false);
      if (!hasOutgoingPending) {
        return thread;
      }

      return {
        ...thread,
        messages: thread.messages.map((message) => (
          message.mine ? { ...message, readByRecipient: true } : message
        )),
      };
    }));
  };

  const openThread = (threadId: string) => {
    setActiveThreadId(threadId);
    markIncomingAsRead(threadId);
    markOutgoingAsRead(threadId);
  };

  const nowTimeLabel = () => {
    const now = new Date();
    return now.toLocaleTimeString('pl-PL', { hour: '2-digit', minute: '2-digit' });
  };

  const sendAdminMessage = async (value: string) => {
    const tempId = `admin-temp-${Date.now()}`;
    const sentAt = Date.now();

    setThreads((current) => current.map((thread) => (
      thread.id === ADMIN_THREAD_ID
        ? { ...thread, messages: [...thread.messages, { id: tempId, text: value, time: nowTimeLabel(), mine: true, sentAt, readByRecipient: true }] }
        : thread
    )));

    try {
      const sent = await apiFetch<{ id: number; content: string; createdAt: string; mine: boolean }>('/messages/admin', {
        method: 'POST',
        body: JSON.stringify({ content: value }),
      });

      setThreads((current) => current.map((thread) => (
        thread.id === ADMIN_THREAD_ID
          ? {
              ...thread,
              messages: thread.messages.map((message) => (
                message.id === tempId
                  ? { id: `admin-msg-${sent.id}`, text: sent.content, time: nowTimeLabel(), mine: true, sentAt: new Date(sent.createdAt).getTime(), readByRecipient: true }
                  : message
              )),
            }
          : thread
      )));
    } catch {
      setModerationFeedback('Nie udało się wysłać wiadomości. Spróbuj ponownie.');
    }
  };

  const blockedAttachmentPatterns = ['nude', 'nago', 'porn', 'porno', 'sex', 'xxx', 'erot', 'onlyfans'];
  const myBusinessCard: BusinessCard = {
    fullName: 'Mateusz Kaczmarek',
    phone: '+48 501 240 315',
    email: 'mateusz.kaczmarek@polskalokalnie.pl',
  };

  const onChooseAttachment = () => {
    attachmentInputRef.current?.click();
  };

  const onAttachmentSelected = (event: ReactChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0] ?? null;
    event.target.value = '';
    setModerationFeedback('');

    if (!file) {
      return;
    }

    const lowerName = file.name.toLowerCase();
    const hasBlockedKeyword = blockedAttachmentPatterns.some((pattern) => lowerName.includes(pattern));

    if (!file.type.startsWith('image/')) {
      setSelectedAttachment(null);
      setModerationFeedback('Możesz dodać tylko plik zdjęciowy.');
      return;
    }

    if (hasBlockedKeyword) {
      setSelectedAttachment(null);
      setModerationFeedback('Zdjęcie zostało zablokowane przez filtr bezpieczeństwa.');
      return;
    }

    if (file.size > 6 * 1024 * 1024) {
      setSelectedAttachment(null);
      setModerationFeedback('Maksymalny rozmiar zdjęcia to 6 MB.');
      return;
    }

    setSelectedAttachment(file);
    setModerationFeedback('Zdjęcie dodane. Podgląd jest ukryty do czasu moderacji.');
  };

  const sendMessage = async () => {
    if (isActiveThreadBlocked) {
      setModerationFeedback('Ten użytkownik jest zablokowany. Odblokuj go, aby ponownie pisać wiadomości.');
      return;
    }

    const value = composerValue.trim();
    if (!value && !selectedAttachment) {
      return;
    }

    if (activeThread.id === ADMIN_THREAD_ID) {
      void sendAdminMessage(value || 'Wysłano zdjęcie');
      setComposerValue('');
      setSelectedAttachment(null);
      return;
    }

    // Globalny filtr treści (ta sama lista słów co w panelu admina) sprawdzany po stronie backendu.
    if (value) {
      try {
        await apiFetch('/moderation/check', { method: 'POST', body: JSON.stringify({ text: value }) });
      } catch (err) {
        setModerationFeedback(err instanceof Error ? err.message : 'Twoja wiadomość zawiera niedozwolone słownictwo.');
        return;
      }
    }

    const messageId = `${activeThread.id}-${Date.now()}`;
    const sentAt = Date.now();
    const timeLabel = nowTimeLabel();
    const attachmentName = selectedAttachment?.name;

    setThreads((current) => current.map((thread) => {
      if (thread.id !== activeThread.id) {
        return thread;
      }

      return {
        ...thread,
        messages: [
          ...thread.messages,
          {
            id: messageId,
            text: value || 'Wysłano zdjęcie',
            time: timeLabel,
            mine: true,
            readByRecipient: false,
            sentAt,
            attachmentName,
            attachmentModeration: attachmentName ? 'pending' : undefined,
          },
        ],
      };
    }));

    setComposerValue('');
    setSelectedAttachment(null);

    window.setTimeout(() => {
      setThreads((current) => current.map((thread) => {
        if (thread.id !== activeThread.id) {
          return thread;
        }

        return {
          ...thread,
          messages: thread.messages.map((message) => (
            message.id === messageId ? { ...message, readByRecipient: true } : message
          )),
        };
      }));
    }, 4500);

    window.setTimeout(() => {
      const receivedAt = Date.now();
      const receivedTimeLabel = nowTimeLabel();

      setThreads((current) => current.map((thread) => {
        if (thread.id !== activeThread.id) {
          return thread;
        }

        return {
          ...thread,
          status: `Ostatnio aktywna dziś o ${receivedTimeLabel}`,
          messages: [
            ...thread.messages,
            {
              id: `${activeThread.id}-reply-${receivedAt}`,
              text: 'Dziękuję za wiadomość! Odpowiem za chwilę.',
              time: receivedTimeLabel,
              mine: false,
              sentAt: receivedAt,
              unread: true,
            },
          ],
        };
      }));
    }, 9000);
  };

  const sendBusinessCard = () => {
    if (isActiveThreadBlocked) {
      setModerationFeedback('Ten użytkownik jest zablokowany. Odblokuj go, aby ponownie pisać wiadomości.');
      return;
    }

    const messageId = `${activeThread.id}-card-${Date.now()}`;
    const sentAt = Date.now();
    const timeLabel = nowTimeLabel();

    setThreads((current) => current.map((thread) => {
      if (thread.id !== activeThread.id) {
        return thread;
      }

      return {
        ...thread,
        messages: [
          ...thread.messages,
          {
            id: messageId,
            text: 'Wysyłam wizytówkę kontaktową.',
            time: timeLabel,
            mine: true,
            readByRecipient: false,
            sentAt,
            businessCard: myBusinessCard,
          },
        ],
      };
    }));

    window.setTimeout(() => {
      setThreads((current) => current.map((thread) => {
        if (thread.id !== activeThread.id) {
          return thread;
        }

        return {
          ...thread,
          messages: thread.messages.map((message) => (
            message.id === messageId ? { ...message, readByRecipient: true } : message
          )),
        };
      }));
    }, 4500);
  };

  const applyFilter = (filter: ThreadFilter) => {
    setActiveFilter(filter);
    setIsFiltersOpen(false);
  };

  const openBlockModal = () => {
    setIsBlockModalOpen(true);
  };

  const closeBlockModal = () => {
    setIsBlockModalOpen(false);
  };

  const openDeleteChatModal = () => {
    setIsDeleteChatModalOpen(true);
  };

  const closeDeleteChatModal = () => {
    setIsDeleteChatModalOpen(false);
  };

  const confirmToggleBlockUser = () => {
    if (isActiveThreadBlocked) {
      setBlockedThreadIds((current) => current.filter((id) => id !== activeThread.id));
      setModerationFeedback(`Użytkownik ${activeThread.name} został odblokowany.`);
      setIsBlockModalOpen(false);
      return;
    }

    setBlockedThreadIds((current) => (
      current.includes(activeThread.id) ? current : [...current, activeThread.id]
    ));
    setComposerValue('');
    setSelectedAttachment(null);
    setModerationFeedback(`Użytkownik ${activeThread.name} został zablokowany.`);
    setIsBlockModalOpen(false);
  };

  const confirmDeleteChat = () => {
    if (threadItems.length <= 1) {
      setModerationFeedback('Nie można usunąć ostatniego czatu.');
      setIsDeleteChatModalOpen(false);
      return;
    }

    const removedThreadName = activeThread.name;
    const nextThreadId = threadItems.find((thread) => thread.id !== activeThread.id)?.id;

    setThreads((current) => current.filter((thread) => thread.id !== activeThread.id));
    if (nextThreadId) {
      setActiveThreadId(nextThreadId);
    }
    setComposerValue('');
    setSelectedAttachment(null);
    setModerationFeedback(`Czat z użytkownikiem ${removedThreadName} został usunięty.`);
    setIsDeleteChatModalOpen(false);
  };

  const openReportModal = () => {
    setReportStep(1);
    setSelectedReportReason(reportReasons[0].id);
    setReportDetails('');
    setReportAttachments([]);
    setReportAttachmentError('');
    setReportCaseNumber('');
    setReportSubmittedAt('');
    setReportSubmittedReasonTitle('');
    setIsSubmittingReport(false);
    setReportSubmitError('');
    setIsReportModalOpen(true);
  };

  const closeReportModal = () => {
    setReportStep(1);
    setSelectedReportReason(reportReasons[0].id);
    setReportDetails('');
    setReportAttachments([]);
    setReportAttachmentError('');
    setReportCaseNumber('');
    setReportSubmittedAt('');
    setReportSubmittedReasonTitle('');
    setIsSubmittingReport(false);
    setReportSubmitError('');
    setIsReportModalOpen(false);
  };

  const onChooseReportAttachments = () => {
    reportAttachmentInputRef.current?.click();
  };

  const onReportAttachmentsSelected = (event: ReactChangeEvent<HTMLInputElement>) => {
    const files = Array.from(event.target.files ?? []);
    event.target.value = '';

    if (files.length === 0) {
      return;
    }

    setReportSubmitError('');

    const allowedMimeTypes = new Set(['image/png', 'image/jpeg', 'application/pdf']);
    const allowedExtensions = ['.png', '.jpg', '.jpeg', '.pdf'];
    const maxFileSize = 10 * 1024 * 1024;
    const maxFiles = 5;

    const nextFiles = [...reportAttachments];
    let firstError = '';

    files.forEach((file) => {
      if (nextFiles.length >= maxFiles) {
        if (!firstError) {
          firstError = 'Możesz dodać maksymalnie 5 plików.';
        }
        return;
      }

      const fileName = file.name.toLowerCase();
      const hasAllowedExtension = allowedExtensions.some((extension) => fileName.endsWith(extension));
      const hasAllowedMime = allowedMimeTypes.has(file.type);

      if (!hasAllowedExtension && !hasAllowedMime) {
        if (!firstError) {
          firstError = 'Dozwolone formaty plików: PNG, JPG, PDF.';
        }
        return;
      }

      if (file.size > maxFileSize) {
        if (!firstError) {
          firstError = 'Maksymalny rozmiar pojedynczego pliku to 10 MB.';
        }
        return;
      }

      const alreadyAdded = nextFiles.some((addedFile) => (
        addedFile.name === file.name
        && addedFile.size === file.size
        && addedFile.lastModified === file.lastModified
      ));

      if (!alreadyAdded) {
        nextFiles.push(file);
      }
    });

    setReportAttachments(nextFiles);
    setReportAttachmentError(firstError);
  };

  const removeReportAttachment = (index: number) => {
    setReportAttachments((current) => current.filter((_, fileIndex) => fileIndex !== index));
    setReportAttachmentError('');
    setReportSubmitError('');
  };

  const submitReport = async () => {
    if (!canSubmitReport) {
      return;
    }

    if (!selectedReportReasonDetails) {
      setReportSubmitError('Wybierz powód zgłoszenia.');
      return;
    }

    if (needsMoreDetails) {
      setReportSubmitError('Dla opcji „Inny powód” podaj minimum 10 znaków opisu.');
      return;
    }

    setReportSubmitError('');
    setIsSubmittingReport(true);

    try {
      await new Promise<void>((resolve) => {
        window.setTimeout(() => resolve(), 850);
      });

      const submittedAt = new Date();
      const nextCaseNumber = String(submittedAt.getTime()).slice(-6);

      setReportCaseNumber(nextCaseNumber);
      setReportSubmittedReasonTitle(selectedReportReasonDetails.title);
      setReportSubmittedAt(submittedAt.toLocaleString('pl-PL', {
        day: 'numeric',
        month: 'long',
        year: 'numeric',
        hour: '2-digit',
        minute: '2-digit',
      }));
      setReportStep(3);
    } catch {
      setReportSubmitError('Nie udało się wysłać zgłoszenia. Spróbuj ponownie.');
    } finally {
      setIsSubmittingReport(false);
    }
  };

  return (
    <section className="messages-page" aria-label="Wiadomości użytkownika">
      <div className="messages-shell">
        <div className="messages-header">
          <div className="messages-breadcrumb" aria-label="Nawigacja wiadomości">
            <Link to={ROUTES.account}>Panel główny</Link>
            <Icon name="chevron" />
            <Link className="active" to={ROUTES.messages}>Wiadomości</Link>
          </div>
          <h1>Wiadomości</h1>
        </div>

        <div className="messages-grid">
          <aside className="messages-threads">
            <div className="messages-search-row">
              <label className="messages-search-input">
                <input
                  type="text"
                  value={query}
                  onChange={(event) => setQuery(event.target.value)}
                  placeholder="Szukaj w wiadomościach..."
                />
                <Icon name="search" />
              </label>
              <button
                type="button"
                className={isFiltersOpen ? 'active' : ''}
                aria-label="Opcje listy wiadomości"
                aria-expanded={isFiltersOpen}
                onClick={() => setIsFiltersOpen((current) => !current)}
              >
                <Icon name="list" />
              </button>
            </div>

            <div className={isFiltersOpen ? 'messages-filters open' : 'messages-filters'} role="tablist" aria-label="Filtr konwersacji">
              <button type="button" role="tab" aria-selected={activeFilter === 'all'} className={activeFilter === 'all' ? 'active' : ''} onClick={() => applyFilter('all')}>Wszystkie <span>{threadItems.length}</span></button>
              <button type="button" role="tab" aria-selected={activeFilter === 'unread'} className={activeFilter === 'unread' ? 'active' : ''} onClick={() => applyFilter('unread')}>Nieodczytane <span>{unreadThreadsCount}</span></button>
              <button type="button" role="tab" aria-selected={activeFilter === 'read'} className={activeFilter === 'read' ? 'active' : ''} onClick={() => applyFilter('read')}>Odczytane <span>{readThreadsCount}</span></button>
            </div>

            <div className="messages-thread-list">
              {filteredThreads.length === 0 && <p className="messages-empty-list">Brak rozmów dla wybranego filtra.</p>}
              {filteredThreads.map((thread) => (
                <button
                  type="button"
                  key={thread.id}
                  className={thread.id === activeThread.id ? 'active' : ''}
                  onClick={() => openThread(thread.id)}
                >
                  <span className="messages-thread-avatar" style={{ backgroundImage: `url(${thread.avatarImage})` }} aria-hidden="true" />
                  <span className="messages-thread-copy">
                    <strong>{thread.name}</strong>
                    <small>{thread.preview}</small>
                  </span>
                  <span className="messages-thread-meta">
                    <em>{thread.time}</em>
                    {thread.unreadCount > 0 && <i>{thread.unreadCount}</i>}
                  </span>
                </button>
              ))}
            </div>
          </aside>

          <section className="messages-chat" aria-label="Okno rozmowy">
            <header className="messages-chat-head">
              <div className="messages-chat-user">
                <span style={{ backgroundImage: `url(${activeThread.avatarImage})` }} aria-hidden="true" />
                <div>
                  <strong>{activeThread.name}</strong>
                  <small>{activeThread.status}</small>
                </div>
              </div>
              <div className="messages-chat-listing">
                <img src={activeThreadListing?.thumbnail || heroImage} alt="Miniatura ogłoszenia" />
                <div>
                  <strong>{activeThreadListing?.title || 'Czat użytkownika'}</strong>
                  <small>{activeThreadListing ? formatPln(activeThreadListing.price) : 'Brak powiązanego ogłoszenia'}</small>
                </div>
              </div>
            </header>

            <div className="messages-bubbles">
              {activeThreadMessages.map((message) => (
                <div key={message.id} className={message.mine ? 'message-row mine' : 'message-row'}>
                  {!message.mine && <span className="message-avatar" style={{ backgroundImage: `url(${activeThread.avatarImage})` }} aria-hidden="true" />}
                  <article>
                    {message.attachmentName && (
                      <div className="message-attachment-card" role="status" aria-label="Załącznik oczekuje na moderację">
                        <Icon name="shield" />
                        <div>
                          <strong>{message.attachmentName}</strong>
                          <small>Zdjęcie ukryte. Oczekuje na moderację bezpieczeństwa.</small>
                        </div>
                      </div>
                    )}
                    {message.businessCard && (
                      <div className="message-business-card" role="group" aria-label="Wizytówka kontaktowa">
                        <div className="message-business-card-top">
                          <span className="message-business-card-badge"><Icon name="user" /></span>
                          <div>
                            <em>Wizytówka kontaktowa</em>
                            <strong>{message.businessCard.fullName}</strong>
                          </div>
                        </div>
                        <div className="message-business-card-grid">
                          <a href={`tel:${message.businessCard.phone.replace(/\s+/g, '')}`} aria-label={`Zadzwoń: ${message.businessCard.phone}`}>
                            <Icon name="phone" />
                            <span>{message.businessCard.phone}</span>
                          </a>
                          <a href={`mailto:${message.businessCard.email}`} aria-label={`Wyślij e-mail: ${message.businessCard.email}`}>
                            <Icon name="mail" />
                            <span>{message.businessCard.email}</span>
                          </a>
                        </div>
                      </div>
                    )}
                    {message.text && <p>{message.text}</p>}
                    <small className={message.mine ? (message.readByRecipient ? 'message-meta read' : 'message-meta pending') : ''}>
                      {message.time}
                      {message.mine && (
                        <span className={message.readByRecipient ? 'message-checks read' : 'message-checks pending'} aria-label={message.readByRecipient ? 'Odczytano' : 'Wysłano'}>
                          <Icon name="check" />
                          {message.readByRecipient && <Icon name="check" />}
                        </span>
                      )}
                    </small>
                  </article>
                </div>
              ))}
            </div>

            <footer className="messages-composer">
              <button type="button" aria-label="Dodaj załącznik" onClick={onChooseAttachment}><Icon name="upload" /></button>
              <button type="button" className="card-action" aria-label="Wyślij wizytówkę" onClick={sendBusinessCard} disabled={isActiveThreadBlocked}>Wizytówka</button>
              <input ref={attachmentInputRef} type="file" accept="image/*" onChange={onAttachmentSelected} disabled={isActiveThreadBlocked} hidden />
              <input
                type="text"
                value={composerValue}
                disabled={isActiveThreadBlocked}
                onChange={(event) => setComposerValue(event.target.value)}
                onKeyDown={(event) => {
                  if (event.key === 'Enter') {
                    event.preventDefault();
                    sendMessage();
                  }
                }}
                placeholder={isActiveThreadBlocked ? 'Użytkownik zablokowany. Wysyłanie wiadomości jest wyłączone.' : `Napisz wiadomość do ${activeThread.name}...`}
              />
              <button type="button" className="send" aria-label="Wyślij wiadomość" onClick={sendMessage} disabled={isActiveThreadBlocked}><Icon name="arrow" /></button>
            </footer>
            {selectedAttachment && <p className="messages-composer-note">Załącznik: {selectedAttachment.name}</p>}
            {moderationFeedback && <p className="messages-composer-note moderation">{moderationFeedback}</p>}
          </section>

          <aside className="messages-details">
            <h2>Szczegóły</h2>
            <article>
              <img src={activeThreadListing?.thumbnail || heroImage} alt="Podgląd ogłoszenia" />
              <div>
                <strong>{activeThreadListing?.title || 'Brak powiązanego ogłoszenia'}</strong>
                <em>{activeThreadListing ? formatPln(activeThreadListing.price) : '-'}</em>
                <p>{activeThreadListingLocation || 'Szczegóły lokalizacji niedostępne'}</p>
                <small>{activeThreadListingSpecs || 'Brak szczegółów technicznych'}</small>
              </div>
            </article>
            <button
              type="button"
              onClick={() => {
                if (activeThreadListing) {
                  onOpenListing(activeThreadListing.listingId);
                }
              }}
              disabled={!activeThreadListing}
            ><span><Icon name="house" /> Zobacz ogłoszenie</span><Icon name="link" /></button>
            <button type="button" onClick={openBlockModal}><span><Icon name="lock" /> {isActiveThreadBlocked ? 'Odblokuj użytkownika' : 'Zablokuj użytkownika'}</span></button>
            <button type="button" onClick={openReportModal}><span><Icon name="warning" /> Zgłoś użytkownika</span></button>
            <button type="button" onClick={openDeleteChatModal}><span><Icon name="trash" /> Usuń czat</span></button>
            <div className="messages-safety-tip">
              <strong><Icon name="shield" /> Pamiętaj o bezpieczeństwie!</strong>
              <p>Nigdy nie podawaj swoich danych osobowych, numeru konta ani kodów BLIK na czacie.</p>
            </div>
          </aside>
        </div>

        {isDeleteChatModalOpen && (
          <div className="delete-chat-overlay" role="dialog" aria-modal="true" aria-label="Usuń czat">
            <div className="delete-chat-modal">
              <button type="button" className="delete-chat-close" onClick={closeDeleteChatModal}>×</button>
              <span className="delete-chat-badge" aria-hidden="true"><Icon name="trash" /></span>
              <h2>Usuń czat</h2>
              <p>Czy na pewno chcesz usunąć czat z użytkownikiem <strong>{activeThread.name}</strong>? Tej operacji nie można cofnąć.</p>
              <div className="delete-chat-actions">
                <button type="button" className="delete-chat-confirm" onClick={confirmDeleteChat}>Tak, usuń</button>
                <button type="button" className="delete-chat-cancel" onClick={closeDeleteChatModal}>Anuluj</button>
              </div>
            </div>
          </div>
        )}

        {isBlockModalOpen && (
          <div className="block-user-overlay" role="dialog" aria-modal="true" aria-label={isActiveThreadBlocked ? 'Odblokuj użytkownika' : 'Zablokuj wszelką komunikację'}>
            <div className="block-user-modal">
              <button type="button" className="block-user-close" onClick={closeBlockModal}>×</button>
              <span className="block-user-badge" aria-hidden="true"><Icon name="lock" /></span>
              <h2>{isActiveThreadBlocked ? 'Odblokuj użytkownika' : 'Zablokuj wszelką komunikację'}</h2>
              <p>
                {isActiveThreadBlocked
                  ? <>Czy chcesz przywrócić możliwość komunikacji z użytkownikiem <strong>{activeThread.name}</strong>?</>
                  : <>Czy chcesz zablokować wszelką komunikację z użytkownikiem <strong>{activeThread.name}</strong>?</>}
              </p>
              <div className="block-user-actions">
                <button type="button" className="block-user-confirm" onClick={confirmToggleBlockUser}>
                  {isActiveThreadBlocked ? 'Tak, odblokuj' : 'Tak, zablokuj'}
                </button>
                <button type="button" className="block-user-cancel" onClick={closeBlockModal}>Anuluj</button>
              </div>
            </div>
          </div>
        )}

        {isReportModalOpen && (
          <div className="report-user-overlay" role="dialog" aria-modal="true" aria-label="Zgłoś użytkownika">
            <div className={reportStep === 3 ? 'report-user-modal step-3' : 'report-user-modal'}>
              <button type="button" className="report-user-close" onClick={closeReportModal} disabled={isSubmittingReport}>×</button>

              <h2>Zgłoś użytkownika</h2>

              {reportStep !== 3 && (
                <div className="report-user-progress" aria-label="Postęp zgłoszenia">
                  <span className={reportStep >= 1 ? 'report-user-progress-segment active' : 'report-user-progress-segment'} />
                  <span className={reportStep >= 2 ? 'report-user-progress-segment active' : 'report-user-progress-segment'} />
                  <span className={reportStep >= 3 ? 'report-user-progress-segment active' : 'report-user-progress-segment'} />
                </div>
              )}

              {reportStep === 1 && (
                <>
                  <h3>Dlaczego zgłaszasz tego użytkownika?</h3>
                  <p className="report-user-anon-note">Twoje zgłoszenie jest anonimowe. Dziękujemy za pomoc w utrzymaniu bezpiecznej społeczności.</p>

                  <div className="report-user-reasons" role="radiogroup" aria-label="Wybierz powód zgłoszenia">
                    {reportReasons.map((reason) => (
                      <button
                        type="button"
                        key={reason.id}
                        className={selectedReportReason === reason.id ? 'active' : ''}
                        role="radio"
                        aria-checked={selectedReportReason === reason.id}
                        onClick={() => {
                          setSelectedReportReason(reason.id);
                          setReportSubmitError('');
                        }}
                      >
                        <span className="report-user-reason-icon"><Icon name={reason.icon} /></span>
                        <span className="report-user-reason-copy">
                          <strong>{reason.title}</strong>
                          <small>{reason.description}</small>
                        </span>
                        <i />
                      </button>
                    ))}
                  </div>

                  <button type="button" className="report-user-primary" onClick={() => setReportStep(2)}>Dalej</button>
                </>
              )}

              {reportStep === 2 && (
                <>
                  <h3 className="report-user-step2-title">Podaj więcej szczegółów <em>(opcjonalnie)</em></h3>
                  <p className="report-user-step2-intro">Im więcej informacji przekażesz, tym łatwiej będzie nam rozpatrzyć zgłoszenie.</p>

                  <label className="report-user-field-label" htmlFor="report-details">Dodatkowe informacje</label>

                  <div className="report-user-textarea-wrap">
                    <textarea
                      id="report-details"
                      className="report-user-textarea"
                      placeholder="Opisz, co się stało..."
                      value={reportDetails}
                      onChange={(event) => {
                        setReportDetails(event.target.value);
                        setReportSubmitError('');
                      }}
                      maxLength={500}
                    />
                    <small>{reportDetails.length}/500</small>
                  </div>

                  {needsMoreDetails && <p className="report-user-error">Dla opcji „Inny powód” wpisz minimum 10 znaków opisu.</p>}

                  <label className="report-user-field-label">Dodaj zrzuty ekranu (opcjonalnie)</label>
                  <p className="report-user-upload-note">Możesz dodać maks. 5 plików</p>

                  <button type="button" className="report-user-upload" onClick={onChooseReportAttachments}>
                    <span><Icon name="document" /></span>
                    <strong>Kliknij lub przeciągnij pliki tutaj</strong>
                    <small>PNG, JPG, PDF do 10 MB</small>
                  </button>
                  <input
                    ref={reportAttachmentInputRef}
                    type="file"
                    accept=".png,.jpg,.jpeg,.pdf,image/png,image/jpeg,application/pdf"
                    multiple
                    onChange={onReportAttachmentsSelected}
                    hidden
                  />

                  {reportAttachments.length > 0 && (
                    <div className="report-user-files" aria-label="Dodane pliki">
                      {reportAttachments.map((file, index) => (
                        <p key={`${file.name}-${file.lastModified}`}>
                          <span>{file.name}</span>
                          <button type="button" onClick={() => removeReportAttachment(index)} aria-label={`Usuń plik ${file.name}`}>Usuń</button>
                        </p>
                      ))}
                    </div>
                  )}

                  {reportAttachmentError && <p className="report-user-error">{reportAttachmentError}</p>}
                  {reportSubmitError && <p className="report-user-error">{reportSubmitError}</p>}

                  <div className="report-user-actions">
                    <button type="button" className="report-user-secondary" onClick={() => setReportStep(1)} disabled={isSubmittingReport}>Wróć</button>
                    <button type="button" className="report-user-primary" onClick={() => void submitReport()} disabled={!canSubmitReport}>
                      {isSubmittingReport ? 'Wysyłanie...' : 'Dalej'}
                    </button>
                  </div>
                </>
              )}

              {reportStep === 3 && (
                <>
                  <div className="report-user-finish-hero" aria-hidden="true">
                    <span className="report-user-finish-icon"><Icon name="shield" /></span>
                  </div>

                  <h3 className="report-user-finish-title">Dziękujemy!<br />Twoje zgłoszenie zostało wysłane.</h3>
                  <p className="report-user-finish-intro">Sprawdzimy je tak szybko, jak to możliwe.<br />W razie potrzeby podejmiemy odpowiednie kroki.</p>

                  <div className="report-user-summary">
                    <strong>Podsumowanie zgłoszenia</strong>
                    <div className="report-user-summary-grid">
                      <span>Powód zgłoszenia:</span>
                      <b>{reportSubmittedReasonTitle || selectedReportReasonDetails?.title || 'Nie podano'}</b>
                      <span>Data zgłoszenia:</span>
                      <b>{reportSubmittedAt || 'Nie podano'}</b>
                    </div>
                    <p><Icon name="lock" /> Zgłoszenie jest anonimowe</p>
                    <small>Numer sprawy: #{reportCaseNumber || String(Date.now()).slice(-6)}</small>
                  </div>

                  <button type="button" className="report-user-primary" onClick={closeReportModal}>Zakończ</button>
                </>
              )}
            </div>
          </div>
        )}
      </div>
    </section>
  );
}

function FavoritesPage({
  onOpenListing,
  onStartChat,
  favoritesState,
  setFavoritesState,
  preferredTab,
}: {
  onOpenListing: (id: number) => void;
  onStartChat: (target: MessageThreadTarget) => void;
  favoritesState: FavoriteListing[];
  setFavoritesState: Dispatch<SetStateAction<FavoriteListing[]>>;
  preferredTab: FavoriteTab;
}) {
  const navigate = useNavigate();
  const { user } = useAuth();
  const [activeTab, setActiveTab] = useState<FavoriteTab>('buy');
  const [menuListingId, setMenuListingId] = useState<string | null>(null);
  const [favoritesFeedback, setFavoritesFeedback] = useState('');
  const [contactPreview, setContactPreview] = useState<{ listingTitle: string; sellerName: string; phone: string } | null>(null);
  const [contactLoadingListingId, setContactLoadingListingId] = useState<number | null>(null);
  const [messageLoadingListingId, setMessageLoadingListingId] = useState<number | null>(null);
  const [sortMode, setSortMode] = useState<'' | 'newest' | 'priceAsc' | 'priceDesc' | 'areaDesc'>('');
  const [filtersOpen, setFiltersOpen] = useState(false);
  const [searchQuery, setSearchQuery] = useState('');
  const [onlyManyPhotos, setOnlyManyPhotos] = useState(false);
  const [onlyThreePlusRooms, setOnlyThreePlusRooms] = useState(false);
  const [onlyRecent, setOnlyRecent] = useState(false);
  const totalFavoritesCount = favoritesState.length;
  const buyFavoritesCount = favoritesState.filter((item) => item.mode === 'buy').length;
  const rentFavoritesCount = favoritesState.filter((item) => item.mode === 'rent').length;
  const activeFiltersCount = Number(onlyManyPhotos) + Number(onlyThreePlusRooms) + Number(onlyRecent);
  const hasActiveControls = sortMode !== '' || searchQuery.trim().length > 0 || activeFiltersCount > 0;

  const parsePriceValue = (price: string) => {
    const normalized = price
      .toLowerCase()
      .replace('/ mies.', '')
      .replace('/mies.', '')
      .replace(/\s+/g, '')
      .replace(',', '.')
      .replace('zł', '')
      .replace(/[^\d.]/g, '');

    const value = Number(normalized);
    return Number.isFinite(value) ? value : 0;
  };

  const parseAreaValue = (area: string) => {
    const numeric = Number(area.replace(',', '.').replace(/[^\d.]/g, ''));
    return Number.isFinite(numeric) ? numeric : 0;
  };

  const parseRoomsValue = (rooms: string) => {
    const match = rooms.match(/\d+/);
    return match ? Number(match[0]) : 0;
  };

  const parseAddedRank = (added: string) => {
    const normalized = added.toLowerCase();

    if (normalized.includes('dzisiaj')) {
      return 100;
    }
    if (normalized.includes('wczoraj')) {
      return 90;
    }
    if (normalized.includes('tydzień')) {
      return 30;
    }

    const dayMatch = normalized.match(/(\d+)\s*dni/);
    if (dayMatch) {
      return Math.max(1, 100 - Number(dayMatch[1]));
    }

    return 10;
  };

  const visibleListings = useMemo(() => {
    const query = searchQuery.trim().toLowerCase();
    const base = favoritesState.filter((item) => item.mode === activeTab);

    const filtered = base.filter((listing) => {
      if (onlyManyPhotos && listing.photos < 10) {
        return false;
      }
      if (onlyThreePlusRooms && parseRoomsValue(listing.rooms) < 3) {
        return false;
      }
      if (onlyRecent && parseAddedRank(listing.added) < 95) {
        return false;
      }
      if (query && !`${listing.title} ${listing.location}`.toLowerCase().includes(query)) {
        return false;
      }
      return true;
    });

    const sorted = [...filtered];
    if (sortMode === 'priceAsc') {
      sorted.sort((a, b) => parsePriceValue(a.price) - parsePriceValue(b.price));
    } else if (sortMode === 'priceDesc') {
      sorted.sort((a, b) => parsePriceValue(b.price) - parsePriceValue(a.price));
    } else if (sortMode === 'areaDesc') {
      sorted.sort((a, b) => parseAreaValue(b.area) - parseAreaValue(a.area));
    } else if (sortMode === 'newest') {
      sorted.sort((a, b) => parseAddedRank(b.added) - parseAddedRank(a.added));
    }

    return sorted;
  }, [activeTab, favoritesState, onlyManyPhotos, onlyRecent, onlyThreePlusRooms, searchQuery, sortMode]);

  useEffect(() => {
    if (!menuListingId) {
      return;
    }

    const onPointerDown = (event: PointerEvent) => {
      const target = event.target as HTMLElement;
      if (!target.closest('.favorite-actions')) {
        setMenuListingId(null);
      }
    };

    window.addEventListener('pointerdown', onPointerDown);
    return () => window.removeEventListener('pointerdown', onPointerDown);
  }, [menuListingId]);

  useEffect(() => {
    setActiveTab(preferredTab);
  }, [preferredTab]);

  useEffect(() => {
    if (!favoritesFeedback) {
      return;
    }

    const timeoutId = window.setTimeout(() => setFavoritesFeedback(''), 5000);
    return () => window.clearTimeout(timeoutId);
  }, [favoritesFeedback]);

  const removeFavoriteListing = (id: string) => {
    setFavoritesState((current) => current.filter((listing) => listing.id !== id));
    setMenuListingId((current) => (current === id ? null : current));
    setFavoritesFeedback('Usunięto ogłoszenie z ulubionych.');
  };

  const resetFilters = () => {
    setSearchQuery('');
    setOnlyManyPhotos(false);
    setOnlyThreePlusRooms(false);
    setOnlyRecent(false);
    setSortMode('');
    setFavoritesFeedback('Wyczyszczono filtry i sortowanie.');
  };

  const handleShowContact = async (listing: FavoriteListing) => {
    setMenuListingId(null);
    setFavoritesFeedback('');
    setContactLoadingListingId(listing.listingId);

    try {
      const fullListing = await apiFetch<ApiListingDetail>(`/listings/${listing.listingId}`);
      const phone = fullListing.contactPhone?.trim();
      if (!phone) {
        setFavoritesFeedback('Ten użytkownik nie udostępnił numeru telefonu.');
        return;
      }

      setContactPreview({
        listingTitle: fullListing.title,
        sellerName: fullListing.contactName?.trim() || 'Administracja Mieszko',
        phone,
      });
    } catch {
      setFavoritesFeedback('Nie udało się pobrać numeru telefonu. Spróbuj ponownie.');
    } finally {
      setContactLoadingListingId(null);
    }
  };

  const handleStartMessage = async (listing: FavoriteListing) => {
    setMenuListingId(null);
    setFavoritesFeedback('');
    setMessageLoadingListingId(listing.listingId);

    try {
      const fullListing = await apiFetch<ApiListingDetail>(`/listings/${listing.listingId}`);
      onStartChat(buildMessageThreadTargetFromListing(fullListing, user));
    } catch {
      setFavoritesFeedback('Nie udało się otworzyć czatu dla tego ogłoszenia.');
    } finally {
      setMessageLoadingListingId(null);
    }
  };

  const clearAllFavorites = () => {
    if (favoritesState.length === 0) {
      setFavoritesFeedback('Brak ogłoszeń w ulubionych.');
      return;
    }

    const confirmed = window.confirm('Czy na pewno chcesz usunąć wszystkie ulubione ogłoszenia?');
    if (!confirmed) {
      return;
    }

    setFavoritesState([]);
    setMenuListingId(null);
    setFavoritesFeedback('Usunięto wszystkie ogłoszenia z ulubionych.');
  };

  const moveListing = (id: string, direction: 'top' | 'bottom') => {
    setFavoritesState((current) => {
      const itemIndex = current.findIndex((listing) => listing.id === id);
      if (itemIndex === -1) {
        return current;
      }

      const item = current[itemIndex];
      const updated = [...current];
      updated.splice(itemIndex, 1);

      if (direction === 'top') {
        const firstSameModeIndex = updated.findIndex((listing) => listing.mode === item.mode);
        updated.splice(firstSameModeIndex === -1 ? updated.length : firstSameModeIndex, 0, item);
        return updated;
      }

      let lastSameModeIndex = -1;
      for (let index = 0; index < updated.length; index += 1) {
        if (updated[index].mode === item.mode) {
          lastSameModeIndex = index;
        }
      }
      updated.splice(lastSameModeIndex === -1 ? updated.length : lastSameModeIndex + 1, 0, item);
      return updated;
    });
  };

  return (
    <section className="favorites-page" aria-label="Ulubione ogłoszenia">
      <div className="favorites-shell">
        <div className="favorites-head">
          <div>
            <h1>Ulubione ogłoszenia</h1>
            <p>Zapisane ogłoszenia, które Cię zainteresowały.</p>
          </div>
          <span className="favorites-saved-pill"><Icon name="heart" /> {totalFavoritesCount} zapisanych</span>
        </div>

        <div className="favorites-toolbar">
          <div className="favorites-tabs" role="tablist" aria-label="Rodzaj ulubionych ogłoszeń">
            <button type="button" role="tab" aria-selected={activeTab === 'buy'} className={activeTab === 'buy' ? 'active' : ''} onClick={() => setActiveTab('buy')}>Kupuję ({buyFavoritesCount})</button>
            <button type="button" role="tab" aria-selected={activeTab === 'rent'} className={activeTab === 'rent' ? 'active' : ''} onClick={() => setActiveTab('rent')}>Wynajmuję ({rentFavoritesCount})</button>
          </div>

          <div className="favorites-controls">
            <label className="favorites-sort-control">
              <span>Sortuj:</span>
              <select value={sortMode} onChange={(event) => setSortMode(event.target.value as '' | 'newest' | 'priceAsc' | 'priceDesc' | 'areaDesc')}>
                <option value="" disabled hidden>Wybierz</option>
                <option value="newest">Dodane najpierw</option>
                <option value="priceAsc">Cena rosnąco</option>
                <option value="priceDesc">Cena malejąco</option>
                <option value="areaDesc">Największy metraż</option>
              </select>
            </label>
            <button type="button" className={`favorites-filter-button ${filtersOpen ? 'active' : ''}`} onClick={() => setFiltersOpen((current) => !current)}><Icon name="list" /> Filtry {activeFiltersCount > 0 ? `(${activeFiltersCount})` : ''}</button>
            {hasActiveControls && <button type="button" className="favorites-filter-button subtle" onClick={resetFilters}>Wyczyść filtry</button>}
          </div>
        </div>

        {filtersOpen && (
          <section className="favorites-filters-panel" aria-label="Filtry ulubionych ogłoszeń">
            <label className="favorites-search-input">
              <Icon name="search" />
              <input
                type="text"
                placeholder="Szukaj po tytule lub lokalizacji"
                value={searchQuery}
                onChange={(event) => setSearchQuery(event.target.value)}
              />
            </label>
            <div className="favorites-quick-toggles">
              <button type="button" className={onlyManyPhotos ? 'active' : ''} aria-pressed={onlyManyPhotos} onClick={() => setOnlyManyPhotos((current) => !current)}><Icon name="camera" /> Min. 10 zdjęć</button>
              <button type="button" className={onlyThreePlusRooms ? 'active' : ''} aria-pressed={onlyThreePlusRooms} onClick={() => setOnlyThreePlusRooms((current) => !current)}><Icon name="group" /> Min. 3 pokoje</button>
              <button type="button" className={onlyRecent ? 'active' : ''} aria-pressed={onlyRecent} onClick={() => setOnlyRecent((current) => !current)}><Icon name="clock" /> Dodane ostatnio</button>
            </div>
          </section>
        )}
        {favoritesFeedback && <p className="favorites-feedback">{favoritesFeedback}</p>}

        <div className="favorites-layout">
          <div className="favorites-list">
            {visibleListings.length === 0 && (
              <article className="favorite-card empty" aria-live="polite">
                <div className="favorite-content">
                  <h3>Brak ogłoszeń w tej kategorii</h3>
                  <p>{searchQuery || activeFiltersCount > 0 ? 'Zmień filtry lub frazę wyszukiwania, aby zobaczyć więcej ofert.' : 'Dodaj nowe oferty do ulubionych, aby pojawiły się na tej liście.'}</p>
                  <Link className="favorite-empty-cta" to={activeTab === 'buy' ? ROUTES.buy : ROUTES.rent}><Icon name="plus" /> {activeTab === 'buy' ? 'Przejdź do ofert Kupuję' : 'Przejdź do ofert Wynajmuję'}</Link>
                </div>
              </article>
            )}
            {visibleListings.map((listing, index) => {
              const isMenuOpen = menuListingId === listing.id;
              const canMoveToTop = index > 0;
              const canMoveToBottom = index < visibleListings.length - 1;

              return (
              <article
                key={listing.id}
                className="favorite-card"
                role="button"
                tabIndex={0}
                onClick={() => onOpenListing(listing.listingId)}
                onKeyDown={(event) => {
                  if (event.key === 'Enter' || event.key === ' ') {
                    event.preventDefault();
                    onOpenListing(listing.listingId);
                  }
                }}
              >
                <div className="favorite-photo" style={{ backgroundImage: `url(${listing.image})` }}>
                  <span className={`favorite-badge ${listing.mode}`}>{listing.badge}</span>
                  <span className="favorite-photos-count"><Icon name="camera" /> {listing.photos}</span>
                </div>

                <div className="favorite-content">
                  <strong>{listing.price}</strong>
                  <h3>{listing.title}</h3>
                  <p><Icon name="pin" /> {listing.location}</p>
                  <ul>
                    <li><Icon name="house" /> {listing.area}</li>
                    <li><Icon name="group" /> {listing.rooms}</li>
                    <li><Icon name="list" /> {listing.floor}</li>
                    <li><Icon name="calendar" /> {listing.year}</li>
                  </ul>
                  <small>{listing.added}</small>
                </div>

                <div className="favorite-actions">
                  <button
                    type="button"
                    className="favorite-like"
                    aria-label="Usuń z ulubionych"
                    onClick={(event) => {
                      event.stopPropagation();
                      removeFavoriteListing(listing.id);
                    }}
                  ><Icon name="heart-filled" /></button>
                  <button
                    type="button"
                    className="favorite-more"
                    aria-label="Więcej opcji"
                    aria-expanded={isMenuOpen}
                    onClick={(event) => {
                      event.stopPropagation();
                      setMenuListingId((current) => (current === listing.id ? null : listing.id));
                    }}
                  >
                    <Icon name="dots-vertical" />
                  </button>

                  {isMenuOpen && (
                    <div className="favorite-menu" role="menu" aria-label="Opcje ogłoszenia">
                      <button
                        type="button"
                        role="menuitem"
                        disabled={messageLoadingListingId === listing.listingId || contactLoadingListingId === listing.listingId}
                        onClick={(event) => {
                          event.stopPropagation();
                          void handleStartMessage(listing);
                        }}
                      >
                        {messageLoadingListingId === listing.listingId ? 'Otwieranie czatu...' : 'Napisz wiadomość'}
                      </button>
                      <button
                        type="button"
                        role="menuitem"
                        disabled={messageLoadingListingId === listing.listingId || contactLoadingListingId === listing.listingId}
                        onClick={(event) => {
                          event.stopPropagation();
                          void handleShowContact(listing);
                        }}
                      >
                        {contactLoadingListingId === listing.listingId ? 'Pobieranie numeru...' : 'Skontaktuj się'}
                      </button>
                      <button
                        type="button"
                        role="menuitem"
                        disabled={!canMoveToTop || (sortMode !== '' && sortMode !== 'newest')}
                        onClick={() => {
                          moveListing(listing.id, 'top');
                          setMenuListingId(null);
                          setFavoritesFeedback('Przeniesiono ogłoszenie na górę listy.');
                        }}
                      >
                        Przenieś na górę
                      </button>
                      <button
                        type="button"
                        role="menuitem"
                        disabled={!canMoveToBottom || (sortMode !== '' && sortMode !== 'newest')}
                        onClick={() => {
                          moveListing(listing.id, 'bottom');
                          setMenuListingId(null);
                          setFavoritesFeedback('Przeniesiono ogłoszenie na dół listy.');
                        }}
                      >
                        Przenieś na dół
                      </button>
                    </div>
                  )}
                </div>
              </article>
            );
            })}

          </div>

          <aside className="favorites-sidebar">
            <section className="favorites-side-card">
              <h2>Zarządzaj ulubionymi</h2>
              <ul>
                <li><span><Icon name="heart" /> Wszystkie ulubione</span><strong>{totalFavoritesCount}</strong></li>
                <li><span><Icon name="house" /> Na sprzedaż</span><strong>{buyFavoritesCount}</strong></li>
                <li><span><Icon name="key" /> Na wynajem</span><strong>{rentFavoritesCount}</strong></li>
              </ul>
              <button type="button" onClick={clearAllFavorites}><Icon name="trash" /> Usuń wszystkie</button>
            </section>

            <section className="favorites-side-note">
              <span className="favorites-side-note-icon"><Icon name="heart" /></span>
              <div className="favorites-side-note-copy">
                <strong>Zapisuj ogłoszenia, które Ci się podobają</strong>
                <p>Dodawaj oferty do ulubionych i wracaj do nich w każdej chwili.</p>
              </div>
            </section>
          </aside>
        </div>
      </div>

      {contactPreview && (
        <div className="favorites-contact-overlay" role="dialog" aria-modal="true" aria-label="Dane kontaktowe sprzedającego" onClick={() => setContactPreview(null)}>
          <div className="favorites-contact-modal" onClick={(event) => event.stopPropagation()}>
            <button type="button" className="favorites-contact-close" onClick={() => setContactPreview(null)} aria-label="Zamknij panel kontaktu">×</button>
            <span className="favorites-contact-badge"><Icon name="phone" /></span>
            <h2>Kontakt do sprzedającego</h2>
            <p><strong>{contactPreview.sellerName}</strong><small>{contactPreview.listingTitle}</small></p>
            <a href={`tel:${contactPreview.phone.replace(/\s+/g, '')}`} className="favorites-contact-phone"><Icon name="phone" /> {contactPreview.phone}</a>
            <div className="favorites-contact-actions">
              <button type="button" onClick={() => setContactPreview(null)}>Zamknij</button>
            </div>
          </div>
        </div>
      )}
    </section>
  );
}

function SearchField({ icon, label, value }: { icon: string; label: string; value: string }) {
  return (
    <button className="search-field">
      <Icon name={icon} />
      <span>
        {label && <small>{label}</small>}
        <strong>{value}</strong>
      </span>
    </button>
  );
}

function AddSelect({ label, value, options = [value], onChange }: { label: string; value: string; options?: string[]; onChange?: (value: string) => void }) {
  return (
    <label className="add-field">
      <span>{label}</span>
      <select {...(onChange ? { value, onChange: (event) => onChange(event.target.value) } : { defaultValue: value })}>
        {options.map((option) => (
          <option key={option} value={option}>{option}</option>
        ))}
      </select>
    </label>
  );
}

function AddInput({ label, value, suffix, type = 'text', placeholder, readOnly, onChange }: { label: string; value: string; suffix?: string; type?: string; placeholder?: string; readOnly?: boolean; onChange?: (value: string) => void }) {
  return (
    <label className="add-field">
      <span>{label}</span>
      <div className="input-with-suffix">
        <input type={type} placeholder={placeholder} readOnly={readOnly} {...(onChange || readOnly ? { value, onChange: (event) => onChange?.(event.target.value) } : { defaultValue: value })} />
        {suffix && <em>{suffix}</em>}
      </div>
    </label>
  );
}

function FilterGroup({ title, children }: { title: string; children: ReactNode }) {
  return (
    <div className="filter-group">
      <h3>{title}</h3>
      {children}
    </div>
  );
}

function CheckRow({ label, checked = false }: { label: string; checked?: boolean }) {
  return (
    <label className="check-row">
      <input type="checkbox" defaultChecked={checked} />
      <span>{label}</span>
    </label>
  );
}

function createSatelliteTiles({
  centerLat,
  centerLng,
  height,
  width,
  zoom,
}: {
  centerLat: number;
  centerLng: number;
  height: number;
  width: number;
  zoom: number;
}) {
  const tileSize = 256;
  const scale = 2 ** zoom;
  const centerX = ((centerLng + 180) / 360) * scale * tileSize;
  const latRad = (centerLat * Math.PI) / 180;
  const centerY =
    ((1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2) *
    scale *
    tileSize;
  const topLeftX = centerX - width / 2;
  const topLeftY = centerY - height / 2;
  const startX = Math.floor(topLeftX / tileSize);
  const endX = Math.floor((topLeftX + width) / tileSize);
  const startY = Math.floor(topLeftY / tileSize);
  const endY = Math.floor((topLeftY + height) / tileSize);
  const tiles = [];

  for (let x = startX; x <= endX; x += 1) {
    for (let y = startY; y <= endY; y += 1) {
      tiles.push({
        left: x * tileSize - topLeftX,
        top: y * tileSize - topLeftY,
        x,
        y,
        z: zoom,
      });
    }
  }

  return tiles;
}

const OFFER_TYPE_LABELS: Record<ApiOfferType, string> = { SALE: 'Sprzedaż', RENT: 'Wynajem' };
const PROPERTY_TYPE_LABELS: Record<ApiPropertyType, string> = { APARTMENT: 'Mieszkanie', HOUSE: 'Dom' };
const LISTING_STATUS_LABELS: Record<ApiListingStatus, string> = {
  PENDING: 'Oczekuje na moderację',
  APPROVED: 'Opublikowane',
  REJECTED: 'Odrzucone',
  PAUSED: 'Wstrzymane',
};

function listingRoomsText(rooms: number): string {
  if (rooms === 1) return '1 pokój';
  if (rooms >= 2 && rooms <= 4) return `${rooms} pokoje`;
  return `${rooms} pokoi`;
}

function listingPerMeter(price: number, area: number): string | null {
  if (!area || area <= 0) return null;
  return `${formatPln(Math.round(price / area)).replace(' zł', '')} zł/m²`;
}

function listingFloorText(floor: string | null, buildingFloors: string | null): string | null {
  if (!floor) return null;
  return buildingFloors ? `${floor}/${buildingFloors}` : floor;
}

function listingRelativeDate(createdAt: string): string {
  const created = new Date(createdAt).getTime();
  if (Number.isNaN(created)) return '';
  const days = Math.floor((Date.now() - created) / (1000 * 60 * 60 * 24));
  if (days <= 0) return 'Dodane dziś';
  if (days === 1) return 'Dodane wczoraj';
  return `Dodane ${days} dni temu`;
}

function listingMarketBadge(market: string | null): string {
  if (!market) {
    return 'Wtórny';
  }
  const normalized = market.toLowerCase().normalize('NFD').replace(/\p{Diacritic}/gu, '');
  if (normalized.includes('pierwot')) {
    return 'Pierwotny';
  }
  if (normalized.includes('wtor')) {
    return 'Wtórny';
  }
  return market;
}

function toMapOfferPreview(summary: ApiListingSummary, detail?: { lat: number | null; lng: number | null } | null): MapOfferPreview {
  const floor = listingFloorText(summary.floor, summary.buildingFloors);
  const meta = [
    `${`${summary.area}`.replace('.', ',')} m²`,
    listingRoomsText(summary.rooms),
    floor ? `${floor} piętro` : null,
  ].filter(Boolean).join(' • ');

  return {
    listingId: summary.id,
    title: summary.title,
    city: summary.city,
    district: [summary.city, summary.district].filter(Boolean).join(', '),
    rooms: summary.rooms,
    area: summary.area,
    priceValue: summary.price,
    market: summary.market === 'pierwotny' ? 'pierwotny' : 'wtorny',
    tags: [listingMarketBadge(summary.market)],
    image: 'hero',
    imageUrl: summary.coverPhoto,
    lat: detail?.lat ?? null,
    lng: detail?.lng ?? null,
    meta,
  };
}

function useLiveListings(offerType: ApiOfferType, refreshMs = LIVE_LISTINGS_REFRESH_MS) {
  const [items, setItems] = useState<LiveListingSummary[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    let cancelled = false;
    let timerId: number | null = null;

    const load = async () => {
      try {
        const summaries = await apiFetch<ApiListingSummary[]>(`/listings?offerType=${offerType}`);
        const resolved = await Promise.all(
          summaries.map(async (summary) => {
            try {
              const detail = await apiFetch<ApiListingDetail>(`/listings/${summary.id}`);
              return {
                ...summary,
                lat: detail.lat,
                lng: detail.lng,
              } as LiveListingSummary;
            } catch {
              return {
                ...summary,
                lat: null,
                lng: null,
              } as LiveListingSummary;
            }
          }),
        );

        if (!cancelled) {
          setItems(resolved);
          setLoading(false);
        }
      } catch {
        if (!cancelled) {
          setLoading(false);
        }
      } finally {
        if (!cancelled) {
          timerId = window.setTimeout(() => {
            void load();
          }, refreshMs);
        }
      }
    };

    setLoading(true);
    void load();

    return () => {
      cancelled = true;
      if (timerId !== null) {
        window.clearTimeout(timerId);
      }
    };
  }, [offerType, refreshMs]);

  return { items, loading };
}

function ListingDetailPage({
  listingId,
  onStartChat,
  isFavorite,
  onToggleFavorite,
  isListingAlertEnabled,
  onToggleListingAlert,
}: {
  listingId: number | null;
  onStartChat: (target: MessageThreadTarget) => void;
  isFavorite: (listingId: number) => boolean;
  onToggleFavorite: (listing: ApiListingDetail) => void;
  isListingAlertEnabled: (listingId: number) => boolean;
  onToggleListingAlert: (listing: ApiListingDetail) => void;
}) {
  const navigate = useNavigate();
  const { user } = useAuth();
  const [listing, setListing] = useState<ApiListingDetail | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [activePhoto, setActivePhoto] = useState(0);
  const [isLightboxOpen, setIsLightboxOpen] = useState(false);
  const [lightboxPhotoIndex, setLightboxPhotoIndex] = useState(0);
  const [isPhoneRevealed, setIsPhoneRevealed] = useState(false);
  const [shareHint, setShareHint] = useState<string | null>(null);
  const [resolvedMapCenter, setResolvedMapCenter] = useState<LatLng | null>(null);
  type ReportStep = 1 | 2 | 3;
  const [isReportModalOpen, setIsReportModalOpen] = useState(false);
  const [reportStep, setReportStep] = useState<ReportStep>(1);
  const [selectedReportReason, setSelectedReportReason] = useState<string>('fraud');
  const [reportDetails, setReportDetails] = useState('');
  const [reportAttachments, setReportAttachments] = useState<File[]>([]);
  const [reportAttachmentError, setReportAttachmentError] = useState('');
  const [reportCaseNumber, setReportCaseNumber] = useState('');
  const [reportSubmittedAt, setReportSubmittedAt] = useState('');
  const [reportSubmittedReasonTitle, setReportSubmittedReasonTitle] = useState('');
  const [reportSubmitting, setReportSubmitting] = useState(false);
  const [reportSubmitError, setReportSubmitError] = useState('');
  const reportAttachmentInputRef = useRef<HTMLInputElement | null>(null);

  const listingReportReasons = [
    {
      id: 'fraud',
      icon: 'warning',
      title: 'Podejrzane lub oszukańcze działania',
      description: 'Ogłoszenie może zawierać próbę wyłudzenia pieniędzy lub fałszywe informacje.',
    },
    {
      id: 'duplicate',
      icon: 'document',
      title: 'Spam lub duplikaty',
      description: 'To ogłoszenie jest duplikatem albo zostało opublikowane wielokrotnie.',
    },
    {
      id: 'wrong',
      icon: 'message',
      title: 'Nieprawidłowe treści',
      description: 'Opis, zdjęcia lub dane kontaktowe są niezgodne z rzeczywistością.',
    },
    {
      id: 'other',
      icon: 'dots',
      title: 'Inny powód',
      description: 'Wskaż inny problem, który wymaga interwencji zespołu moderacji.',
    },
  ];

  useEffect(() => {
    if (listingId == null) {
      setError('Nie wybrano ogłoszenia.');
      setLoading(false);
      return;
    }
    let cancelled = false;
    setLoading(true);
    setError(null);
    setActivePhoto(0);
    apiFetch<ApiListingDetail>(`/listings/${listingId}?incrementView=true`)
      .then((data) => {
        if (!cancelled) {
          setListing(data);
          setLoading(false);
        }
      })
      .catch((err) => {
        if (!cancelled) {
          setError(err instanceof Error ? err.message : 'Nie udało się wczytać ogłoszenia.');
          setLoading(false);
        }
      });
    return () => {
      cancelled = true;
    };
  }, [listingId]);

  useEffect(() => {
    if (!listing) {
      setResolvedMapCenter(null);
      return;
    }
    if (listing.lat != null && listing.lng != null) {
      setResolvedMapCenter(clampCenter({ lat: listing.lat, lng: listing.lng }));
      return;
    }

    const hasAddress = Boolean(listing.city || listing.street || listing.district);
    if (!hasAddress) {
      setResolvedMapCenter(null);
      return;
    }

    const controller = new AbortController();
    geocodeListingLocation({
      city: listing.city,
      district: listing.district ?? '',
      estate: '',
      street: listing.street ?? '',
      building: listing.building ?? '',
      unit: '',
    }, controller.signal)
      .then((point) => {
        if (point) {
          setResolvedMapCenter(clampCenter(point));
        }
      })
      .catch(() => {
        setResolvedMapCenter(null);
      });

    return () => controller.abort();
  }, [listing]);

  const gallery = listing?.photos.length ? listing.photos : [heroImage];

  const openLightbox = (index: number) => {
    setLightboxPhotoIndex(index);
    setIsLightboxOpen(true);
  };

  const closeLightbox = () => {
    setIsLightboxOpen(false);
  };

  const showPreviousLightboxPhoto = () => {
    setLightboxPhotoIndex((current) => (current - 1 + gallery.length) % gallery.length);
  };

  const showNextLightboxPhoto = () => {
    setLightboxPhotoIndex((current) => (current + 1) % gallery.length);
  };

  useEffect(() => {
    if (!isLightboxOpen) {
      return;
    }

    const onKeyDown = (event: KeyboardEvent) => {
      if (event.key === 'Escape') {
        closeLightbox();
        return;
      }
      if (event.key === 'ArrowLeft') {
        showPreviousLightboxPhoto();
        return;
      }
      if (event.key === 'ArrowRight') {
        showNextLightboxPhoto();
      }
    };

    window.addEventListener('keydown', onKeyDown);
    return () => window.removeEventListener('keydown', onKeyDown);
  }, [isLightboxOpen, gallery.length]);

  if (loading) {
    return (
      <section className="listing-detail-page">
        <div className="listing-detail-state">Wczytywanie ogłoszenia…</div>
      </section>
    );
  }

  if (error || !listing) {
    return (
      <section className="listing-detail-page">
        <div className="listing-detail-state">
          <p>{error ?? 'Nie znaleziono ogłoszenia.'}</p>
          <Link className="listing-detail-back" to={ROUTES.buy}>
            <Icon name="arrow" /> Wróć do ofert
          </Link>
        </div>
      </section>
    );
  }
  const perMeter = listingPerMeter(listing.price, listing.area);
  const floorText = listingFloorText(listing.floor, listing.buildingFloors);
  const locationLine = [listing.city, listing.district, listing.street && listing.building ? `${listing.street} ${listing.building}` : listing.street]
    .filter(Boolean)
    .join(', ');
  const sellerName = listing.contactName?.trim() || 'Administracja Mieszko';
  const isOwner = Boolean(user && listing.ownerEmail && user.email.toLowerCase() === listing.ownerEmail.toLowerCase());
  const sellerRole = isOwner ? 'To Twoje ogłoszenie' : 'Oficjalny kontakt zespołem Mieszko';
  const favorite = isFavorite(listing.id);
  const listingAlertEnabled = isListingAlertEnabled(listing.id);
  const virtualTourEmbed = listing.virtualTourUrl ? getVirtualTourEmbed(listing.virtualTourUrl) : null;
  const shareUrl = typeof window !== 'undefined' ? window.location.href : `https://mieszko.pl/oferta/${listing.id}`;

  const copyShareLink = async () => {
    try {
      await navigator.clipboard.writeText(shareUrl);
      setShareHint('Skopiowano link');
      window.setTimeout(() => setShareHint(null), 1800);
    } catch {
      setShareHint('Nie udało się skopiować');
      window.setTimeout(() => setShareHint(null), 1800);
    }
  };

  const openShareWindow = (url: string) => {
    window.open(url, '_blank', 'noopener,noreferrer');
  };

  const revealContact = () => {
    if (listing.contactPhone && listing.contactPhone.trim()) {
      setIsPhoneRevealed(true);
      return;
    }
    onStartChat(buildMessageThreadTargetFromListing(listing, user));
  };

  const startChatWithSeller = () => {
    onStartChat(buildMessageThreadTargetFromListing(listing, user));
  };

  const detailRows: Array<{ label: string; value: string }> = [];
  const pushRow = (label: string, value: string | number | null | undefined) => {
    if (value !== null && value !== undefined && `${value}`.trim() !== '') {
      detailRows.push({ label, value: `${value}` });
    }
  };
  pushRow('Rynek', listing.market);
  pushRow('Cena', formatPln(listing.price));
  if (perMeter) pushRow('Cena za m²', perMeter);
  pushRow('Powierzchnia', `${`${listing.area}`.replace('.', ',')} m²`);
  pushRow('Liczba pokoi', listing.rooms);
  if (floorText) pushRow('Piętro', floorText);
  pushRow('Rok budowy', listing.yearBuilt);
  pushRow('Stan', listing.condition);
  pushRow('Forma własności', listing.ownership);
  pushRow('Typ budynku', listing.buildingType);
  pushRow('Materiał budynku', listing.buildingMaterial);
  pushRow('Okna', listing.windows);
  pushRow('Ekspozycja', listing.exposure);
  pushRow('Wysokość pomieszczeń', listing.roomHeight ? `${listing.roomHeight} m` : null);
  pushRow('Umeblowanie', listing.furnishing);
  pushRow('Dostępne od', listing.availableFrom);
  if (listing.offerType === 'RENT' && listing.rentExtra) pushRow('Czynsz', formatPln(listing.rentExtra));

  const priceSuffix = listing.offerType === 'RENT' ? ' /mc' : '';
  const listingMapCenter = resolvedMapCenter ?? CITY_COORDS[listing.city] ?? POLAND_CENTER;
  const listingMapZoom = listing.street && listing.building ? 16 : listing.street ? 15 : listing.district ? 14 : 12;
  const openLocationInMaps = () => {
    const query = `${listingMapCenter.lat},${listingMapCenter.lng}`;
    window.open(`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(query)}`, '_blank', 'noopener,noreferrer');
  };

  const openReportModal = () => {
    if (!user) {
      navigate(ROUTES.login);
      return;
    }
    setReportStep(1);
    setSelectedReportReason('fraud');
    setReportDetails('');
    setReportAttachments([]);
    setReportAttachmentError('');
    setReportCaseNumber('');
    setReportSubmittedAt('');
    setReportSubmittedReasonTitle('');
    setReportSubmitting(false);
    setReportSubmitError('');
    setIsReportModalOpen(true);
  };

  const closeReportModal = () => {
    if (reportSubmitting) {
      return;
    }
    setReportStep(1);
    setSelectedReportReason('fraud');
    setReportDetails('');
    setReportAttachments([]);
    setReportAttachmentError('');
    setReportCaseNumber('');
    setReportSubmittedAt('');
    setReportSubmittedReasonTitle('');
    setReportSubmitError('');
    setIsReportModalOpen(false);
  };

  const selectedReportReasonDetails = listingReportReasons.find((reason) => reason.id === selectedReportReason);
  const isOtherReasonSelected = selectedReportReason === 'other';
  const normalizedReportDetails = reportDetails.trim();
  const needsMoreDetails = isOtherReasonSelected && normalizedReportDetails.length < 10;
  const canSubmitReport = !reportSubmitting && !reportAttachmentError && !needsMoreDetails;

  const readFileAsDataUrl = (file: File) => new Promise<string>((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => {
      if (typeof reader.result !== 'string') {
        reject(new Error('Nie udało się odczytać pliku.'));
        return;
      }
      resolve(reader.result);
    };
    reader.onerror = () => reject(new Error('Nie udało się odczytać pliku.'));
    reader.readAsDataURL(file);
  });

  const onChooseReportAttachments = () => {
    reportAttachmentInputRef.current?.click();
  };

  const onReportAttachmentsSelected = (event: ReactChangeEvent<HTMLInputElement>) => {
    const files = Array.from(event.target.files ?? []);
    event.target.value = '';

    if (files.length === 0) {
      return;
    }

    setReportSubmitError('');

    const allowedMimeTypes = new Set(['image/png', 'image/jpeg', 'application/pdf']);
    const allowedExtensions = ['.png', '.jpg', '.jpeg', '.pdf'];
    const maxFileSize = 10 * 1024 * 1024;
    const maxFiles = 5;

    const nextFiles = [...reportAttachments];
    let firstError = '';

    files.forEach((file) => {
      if (nextFiles.length >= maxFiles) {
        if (!firstError) {
          firstError = 'Możesz dodać maksymalnie 5 plików.';
        }
        return;
      }

      const fileName = file.name.toLowerCase();
      const hasAllowedExtension = allowedExtensions.some((extension) => fileName.endsWith(extension));
      const hasAllowedMime = allowedMimeTypes.has(file.type);

      if (!hasAllowedExtension && !hasAllowedMime) {
        if (!firstError) {
          firstError = 'Dozwolone formaty plików: PNG, JPG, PDF.';
        }
        return;
      }

      if (file.size > maxFileSize) {
        if (!firstError) {
          firstError = 'Maksymalny rozmiar pojedynczego pliku to 10 MB.';
        }
        return;
      }

      const alreadyAdded = nextFiles.some((addedFile) => (
        addedFile.name === file.name
        && addedFile.size === file.size
        && addedFile.lastModified === file.lastModified
      ));

      if (!alreadyAdded) {
        nextFiles.push(file);
      }
    });

    setReportAttachments(nextFiles);
    setReportAttachmentError(firstError);
  };

  const removeReportAttachment = (index: number) => {
    setReportAttachments((current) => current.filter((_, fileIndex) => fileIndex !== index));
    setReportAttachmentError('');
    setReportSubmitError('');
  };

  const submitReport = async () => {
    if (!canSubmitReport) {
      return;
    }

    if (!selectedReportReasonDetails) {
      setReportSubmitError('Wybierz powód zgłoszenia.');
      return;
    }

    if (needsMoreDetails) {
      setReportSubmitError('Dla opcji „Inny powód” podaj minimum 10 znaków opisu.');
      return;
    }

    setReportSubmitError('');
    setReportSubmitting(true);
    try {
      const attachmentFiles = await Promise.all(reportAttachments.map(async (file) => {
        const fileName = file.name.toLowerCase();
        const isImageByExtension = fileName.endsWith('.png') || fileName.endsWith('.jpg') || fileName.endsWith('.jpeg') || fileName.endsWith('.webp');
        const isImageByMime = file.type.toLowerCase().startsWith('image/');
        const isImage = isImageByMime || isImageByExtension;
        const dataUrl = isImage ? await readFileAsDataUrl(file) : null;
        return {
          fileName: file.name,
          fileType: file.type || null,
          dataUrl,
        };
      }));

      const submitted = await apiFetch<{ id: number; createdAt: string; reasonTitle: string }>('/reports/listings', {
        method: 'POST',
        body: JSON.stringify({
          listingId: listing.id,
          reasonId: selectedReportReason,
          reasonTitle: selectedReportReasonDetails.title,
          details: normalizedReportDetails || null,
          attachmentNames: reportAttachments.map((file) => file.name),
          attachmentFiles,
        }),
      });

      const submittedAt = new Date(submitted.createdAt);

      setReportCaseNumber(String(submitted.id));
      setReportSubmittedReasonTitle(selectedReportReasonDetails.title);
      setReportSubmittedAt(submittedAt.toLocaleString('pl-PL', {
        day: 'numeric',
        month: 'long',
        year: 'numeric',
        hour: '2-digit',
        minute: '2-digit',
      }));
      setReportStep(3);
    } catch (err) {
      const status = (err as Error & { status?: number }).status;
      if (status === 401 || status === 403) {
        setReportSubmitError('Aby wysłać zgłoszenie, zaloguj się na konto użytkownika.');
      } else if (status === 404) {
        setReportSubmitError('Backend nie ma jeszcze endpointu zgłoszeń. Zrestartuj backend (lub docker-compose z build), a następnie spróbuj ponownie.');
      } else {
        setReportSubmitError(err instanceof Error ? err.message : 'Nie udało się wysłać zgłoszenia. Spróbuj ponownie.');
      }
    } finally {
      setReportSubmitting(false);
    }
  };

  return (
    <section className="listing-detail-page">
      <div className="listing-detail-crumbs">
        <Link to={ROUTES.home}>Panel główny</Link>
        <span>›</span>
        <Link to={listing.offerType === 'RENT' ? ROUTES.rent : ROUTES.buy}>
          {listing.offerType === 'RENT' ? 'Wynajmuję' : 'Kupuję'}
        </Link>
        <span>›</span>
        <strong>{listing.title}</strong>
      </div>

      <div className="listing-detail-layout">
        <div className="listing-detail-main">
          <div className="listing-detail-gallery">
            <div className="listing-detail-gallery-main">
              <button
                type="button"
                className="listing-detail-gallery-main-image"
                onClick={() => openLightbox(activePhoto)}
                aria-label="Powiększ zdjęcie"
              >
                <img src={gallery[activePhoto] ?? gallery[0]} alt={listing.title} />
              </button>
              {gallery.length > 1 && (
                <>
                  <button
                    type="button"
                    className="ld-gallery-arrow ld-gallery-prev"
                    onClick={() => setActivePhoto((index) => (index - 1 + gallery.length) % gallery.length)}
                    aria-label="Poprzednie zdjęcie"
                  ><span className="ld-gallery-arrow-icon"><Icon name="chevron" /></span></button>
                  <button
                    type="button"
                    className="ld-gallery-arrow ld-gallery-next"
                    onClick={() => setActivePhoto((index) => (index + 1) % gallery.length)}
                    aria-label="Następne zdjęcie"
                  ><span className="ld-gallery-arrow-icon"><Icon name="chevron" /></span></button>
                </>
              )}
              <div className="listing-detail-badges">
                <span className="ld-badge ld-badge-green">{OFFER_TYPE_LABELS[listing.offerType]}</span>
                <span className="ld-badge">{PROPERTY_TYPE_LABELS[listing.propertyType]}</span>
              </div>
              <span className="listing-detail-gallery-count">{activePhoto + 1} / {gallery.length}</span>
            </div>
            {gallery.length > 1 && (
              <div className="listing-detail-thumbs">
                {gallery.map((photo, index) => (
                  <button
                    type="button"
                    key={index}
                    className={index === activePhoto ? 'active' : ''}
                    onClick={() => {
                      setActivePhoto(index);
                      openLightbox(index);
                    }}
                    aria-label={`Zdjęcie ${index + 1}`}
                  >
                    <img src={photo} alt="" />
                  </button>
                ))}
              </div>
            )}
          </div>

          <div className="listing-detail-card">
            <h2>Szczegóły</h2>
            <div className="listing-detail-specs">
              {detailRows.map((row) => (
                <div className="listing-detail-spec" key={row.label}>
                  <span>{row.label}</span>
                  <strong>{row.value}</strong>
                </div>
              ))}
            </div>
          </div>

          <div className="listing-detail-card">
            <h2>Opis</h2>
            <p className="listing-detail-description">{listing.description}</p>
          </div>

          {listing.virtualTourUrl && (
            <div className="listing-detail-card">
              <h2>Wirtualny spacer 3D</h2>
              {virtualTourEmbed ? (
                <div className="listing-detail-tour-embed">
                  <iframe
                    src={virtualTourEmbed.embedUrl}
                    title={`Wirtualny spacer (${virtualTourEmbed.providerLabel})`}
                    loading="lazy"
                    allow="xr-spatial-tracking; vr; gyroscope; accelerometer; fullscreen"
                    allowFullScreen
                  />
                </div>
              ) : (
                <p className="listing-detail-description">Ten typ linku nie obsługuje osadzenia w karcie. Otwórz spacer w nowej karcie.</p>
              )}
              <a
                className="listing-detail-tour-link"
                href={listing.virtualTourUrl}
                target="_blank"
                rel="noopener noreferrer"
              >
                Otwórz wirtualny spacer <Icon name="arrow" />
              </a>
            </div>
          )}

          <div className="listing-detail-card">
            <h2>Media i udogodnienia</h2>
            <div className="listing-detail-chips">
              {listing.media.map((item) => (
                <span key={`m-${item}`}><Icon name="spark" /> {item}</span>
              ))}
              {listing.amenities.map((item) => (
                <span key={`a-${item}`}><Icon name="check" /> {item}</span>
              ))}
              {listing.media.length === 0 && listing.amenities.length === 0 && (
                <span><Icon name="list" /> Brak dodanych informacji o mediach i udogodnieniach</span>
              )}
            </div>
          </div>

          <div className="listing-detail-card">
            <h2>Lokalizacja</h2>
            <p className="listing-detail-address">{locationLine || listing.city}</p>
            <div className="listing-detail-map">
              <SatelliteMap
                markers={[]}
                center={listingMapCenter}
                cityLabel={listing.city}
                centerMarkerLabel={formatPln(listing.price).replace(' zł', ' zł')}
                initialZoom={listingMapZoom}
                showCityTitle={false}
                onMapClick={openLocationInMaps}
              />
            </div>
            <p className="listing-detail-map-hint">Kliknij mapę, aby otworzyć dokładny punkt w Google Maps.</p>
            <div className="listing-detail-meta-actions">
              <div className="listing-detail-meta">
                <span><Icon name="clock" /> {listingRelativeDate(listing.createdAt)}</span>
                <span>ID ogłoszenia: {listing.id}</span>
                <span><Icon name="eye" /> Wyświetlenia: {(listing.viewsCount ?? 0).toLocaleString('pl-PL')}</span>
                {listing.status !== 'APPROVED' && (
                  <span className={`ld-status ld-status-${listing.status.toLowerCase()}`}>{LISTING_STATUS_LABELS[listing.status]}</span>
                )}
              </div>
              <button type="button" className="listing-detail-report-btn" onClick={openReportModal}>
                <Icon name="warning" /> Zgłoś
              </button>
            </div>
          </div>
        </div>

        <aside className="listing-detail-side">
          <div className="listing-detail-price-card">
            <h1>{listing.title}</h1>
            <p className="listing-detail-loc">{locationLine || listing.city}</p>
            <div className="listing-detail-price-row">
              <strong>{formatPln(listing.price)}{priceSuffix}</strong>
              {perMeter && <span>{perMeter}</span>}
            </div>

            <div className="listing-detail-stats">
              <div><Icon name="screen" /><strong>{`${listing.area}`.replace('.', ',')} m²</strong><span>Powierzchnia</span></div>
              <div><Icon name="sofa" /><strong>{listing.rooms}</strong><span>Pokoje</span></div>
              {floorText && <div><Icon name="building" /><strong>{floorText}</strong><span>Piętro</span></div>}
              {listing.yearBuilt && <div><Icon name="calendar" /><strong>{listing.yearBuilt}</strong><span>Rok budowy</span></div>}
            </div>

            <button type="button" className="listing-detail-primary" onClick={revealContact}>
              {isPhoneRevealed && listing.contactPhone
                ? <><Icon name="phone" /> {listing.contactPhone}</>
                : 'Skontaktuj się ze sprzedającym'}
            </button>

            <button type="button" className="listing-detail-secondary" onClick={startChatWithSeller}>
              <Icon name="message" /> Napisz wiadomość
            </button>

            <div className="listing-detail-seller-card">
              <div className="listing-detail-seller-avatar">{sellerName.slice(0, 1).toUpperCase()}</div>
              <div>
                <strong>{sellerName}</strong>
                <small>{sellerRole}</small>
                <small>Na Mieszko.pl od 2023</small>
                {isPhoneRevealed && listing.contactPhone && (
                  <a href={`tel:${listing.contactPhone.replace(/\s+/g, '')}`}><Icon name="phone" /> {listing.contactPhone}</a>
                )}
              </div>
            </div>

            <Link className="listing-detail-profile-btn" to={ROUTES.accountPublicProfile}>
              Wyświetl profil
            </Link>

            <div className="listing-detail-share">
              <strong>Udostępnij ofertę</strong>
              <div className="listing-detail-share-actions">
                <button type="button" onClick={copyShareLink} aria-label="Kopiuj link"><Icon name="link" /></button>
                <button type="button" onClick={() => openShareWindow(`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}`)} aria-label="Udostępnij na Facebooku">f</button>
                <button type="button" onClick={() => openShareWindow(`https://m.me/?link=${encodeURIComponent(shareUrl)}`)} aria-label="Udostępnij w Messengerze"><Icon name="message" /></button>
                <button type="button" onClick={() => openShareWindow(`https://wa.me/?text=${encodeURIComponent(`${listing.title} ${shareUrl}`)}`)} aria-label="Udostępnij na WhatsApp">wa</button>
                <button
                  type="button"
                  onClick={async () => {
                    if (navigator.share) {
                      await navigator.share({ title: listing.title, text: listing.city, url: shareUrl });
                      return;
                    }
                    await copyShareLink();
                  }}
                  aria-label="Więcej opcji"
                >
                  <Icon name="dots" />
                </button>
              </div>
              {shareHint && <small className="listing-detail-share-hint">{shareHint}</small>}
            </div>

            <button
              type="button"
              className={`listing-detail-fav ${favorite ? 'active' : ''}`}
              onClick={() => onToggleFavorite(listing)}
            >
              <Icon name={favorite ? 'heart-filled' : 'heart'} /> {favorite ? 'W ulubionych' : 'Dodaj do ulubionych'}
            </button>

            <button
              type="button"
              className={`listing-detail-alert ${listingAlertEnabled ? 'active' : ''}`}
              onClick={() => onToggleListingAlert(listing)}
            >
              <Icon name="clock" /> {listingAlertEnabled ? 'Alert aktywny' : 'Utwórz alert ceny'}
            </button>
          </div>
        </aside>
      </div>

      {isReportModalOpen && (
        <div className="report-user-overlay" role="dialog" aria-modal="true" aria-label="Zgłoś ogłoszenie" onClick={closeReportModal}>
          <div className={reportStep === 3 ? 'report-user-modal step-3' : 'report-user-modal'} onClick={(event) => event.stopPropagation()}>
            <button type="button" className="report-user-close" onClick={closeReportModal} disabled={reportSubmitting}>×</button>

            <h2>Zgłoś ogłoszenie</h2>

            {reportStep !== 3 && (
              <div className="report-user-progress" aria-label="Postęp zgłoszenia">
                <span className={reportStep >= 1 ? 'report-user-progress-segment active' : 'report-user-progress-segment'} />
                <span className={reportStep >= 2 ? 'report-user-progress-segment active' : 'report-user-progress-segment'} />
                <span className={reportStep >= 3 ? 'report-user-progress-segment active' : 'report-user-progress-segment'} />
              </div>
            )}

            {reportStep === 1 && (
              <>
                <h3>Dlaczego zgłaszasz to ogłoszenie?</h3>
                <p className="report-user-anon-note">Twoje zgłoszenie jest anonimowe. Dziękujemy za pomoc w utrzymaniu bezpiecznej społeczności.</p>

                <div className="report-user-reasons" role="radiogroup" aria-label="Wybierz powód zgłoszenia">
                  {listingReportReasons.map((reason) => (
                    <button
                      type="button"
                      key={reason.id}
                      className={selectedReportReason === reason.id ? 'active' : ''}
                      role="radio"
                      aria-checked={selectedReportReason === reason.id}
                      onClick={() => {
                        setSelectedReportReason(reason.id);
                        setReportSubmitError('');
                      }}
                    >
                      <span className="report-user-reason-icon"><Icon name={reason.icon} /></span>
                      <span className="report-user-reason-copy">
                        <strong>{reason.title}</strong>
                        <small>{reason.description}</small>
                      </span>
                      <i />
                    </button>
                  ))}
                </div>

                <button type="button" className="report-user-primary" onClick={() => setReportStep(2)}>Dalej</button>
              </>
            )}

            {reportStep === 2 && (
              <>
                <h3 className="report-user-step2-title">Podaj więcej szczegółów <em>(opcjonalnie)</em></h3>
                <p className="report-user-step2-intro">Im więcej informacji przekażesz, tym łatwiej będzie nam rozpatrzyć zgłoszenie ogłoszenia.</p>
                <p className="report-user-step2-intro"><strong>Ogłoszenie:</strong> {listing.title}</p>

                <label className="report-user-field-label" htmlFor="listing-report-details">Dodatkowe informacje</label>

                <div className="report-user-textarea-wrap">
                  <textarea
                    id="listing-report-details"
                    className="report-user-textarea"
                    placeholder="Opisz, co jest nieprawidłowe..."
                    value={reportDetails}
                    onChange={(event) => {
                      setReportDetails(event.target.value);
                      setReportSubmitError('');
                    }}
                    maxLength={500}
                  />
                  <small>{reportDetails.length}/500</small>
                </div>

                {needsMoreDetails && <p className="report-user-error">Dla opcji „Inny powód” wpisz minimum 10 znaków opisu.</p>}

                <label className="report-user-field-label">Dodaj zrzuty ekranu (opcjonalnie)</label>
                <p className="report-user-upload-note">Możesz dodać maks. 5 plików</p>

                <button type="button" className="report-user-upload" onClick={onChooseReportAttachments}>
                  <span><Icon name="document" /></span>
                  <strong>Kliknij lub przeciągnij pliki tutaj</strong>
                  <small>PNG, JPG, PDF do 10 MB</small>
                </button>
                <input
                  ref={reportAttachmentInputRef}
                  type="file"
                  accept=".png,.jpg,.jpeg,.pdf,image/png,image/jpeg,application/pdf"
                  multiple
                  onChange={onReportAttachmentsSelected}
                  hidden
                />

                {reportAttachments.length > 0 && (
                  <div className="report-user-files" aria-label="Dodane pliki">
                    {reportAttachments.map((file, index) => (
                      <p key={`${file.name}-${file.lastModified}`}>
                        <span>{file.name}</span>
                        <button type="button" onClick={() => removeReportAttachment(index)} aria-label={`Usuń plik ${file.name}`}>Usuń</button>
                      </p>
                    ))}
                  </div>
                )}

                {reportAttachmentError && <p className="report-user-error">{reportAttachmentError}</p>}
                {reportSubmitError && <p className="report-user-error">{reportSubmitError}</p>}

                <div className="report-user-actions">
                  <button type="button" className="report-user-secondary" onClick={() => setReportStep(1)} disabled={reportSubmitting}>Wróć</button>
                  <button type="button" className="report-user-primary" onClick={() => void submitReport()} disabled={!canSubmitReport}>
                    {reportSubmitting ? 'Wysyłanie...' : 'Dalej'}
                  </button>
                </div>
              </>
            )}

            {reportStep === 3 && (
              <>
                <div className="report-user-finish-hero" aria-hidden="true">
                  <span className="report-user-finish-icon"><Icon name="shield" /></span>
                </div>

                <h3 className="report-user-finish-title">Dziękujemy!<br />Twoje zgłoszenie zostało wysłane.</h3>
                <p className="report-user-finish-intro">Sprawdzimy je tak szybko, jak to możliwe.<br />W razie potrzeby podejmiemy odpowiednie kroki.</p>

                <div className="report-user-summary">
                  <strong>Podsumowanie zgłoszenia</strong>
                  <div className="report-user-summary-grid">
                    <span>Powód zgłoszenia:</span>
                    <b>{reportSubmittedReasonTitle || selectedReportReasonDetails?.title || 'Nie podano'}</b>
                    <span>Data zgłoszenia:</span>
                    <b>{reportSubmittedAt || 'Nie podano'}</b>
                  </div>
                  <p><Icon name="lock" /> Zgłoszenie jest anonimowe</p>
                  <small>Numer sprawy: #{reportCaseNumber || String(Date.now()).slice(-6)}</small>
                </div>

                <button type="button" className="report-user-primary" onClick={closeReportModal}>Zakończ</button>
              </>
            )}
          </div>
        </div>
      )}

      {isLightboxOpen && (
        <div className="listing-lightbox-overlay" role="dialog" aria-modal="true" aria-label="Podgląd zdjęcia" onClick={closeLightbox}>
          <div className="listing-lightbox-modal" onClick={(event) => event.stopPropagation()}>
            <button type="button" className="listing-lightbox-close" onClick={closeLightbox} aria-label="Zamknij podgląd">×</button>

            {gallery.length > 1 && (
              <button
                type="button"
                className="listing-lightbox-arrow listing-lightbox-prev"
                onClick={showPreviousLightboxPhoto}
                aria-label="Poprzednie zdjęcie"
              >
                <Icon name="chevron" />
              </button>
            )}

            <img className="listing-lightbox-image" src={gallery[lightboxPhotoIndex] ?? gallery[0]} alt={`Zdjęcie ${lightboxPhotoIndex + 1}`} />

            {gallery.length > 1 && (
              <button
                type="button"
                className="listing-lightbox-arrow listing-lightbox-next"
                onClick={showNextLightboxPhoto}
                aria-label="Następne zdjęcie"
              >
                <Icon name="chevron" />
              </button>
            )}

            <span className="listing-lightbox-counter">{lightboxPhotoIndex + 1} / {gallery.length}</span>
          </div>
        </div>
      )}
    </section>
  );
}

function AdminReportDetailsModal({
  report,
  onClose,
  onPreviewListing,
  isImageAttachment,
}: {
  report: AdminListingReport;
  onClose: () => void;
  onPreviewListing: (() => void) | null;
  isImageAttachment: (attachment: AdminListingReportAttachment) => boolean;
}) {
  const attachments = report.attachments ?? [];
  const details = report.details?.trim() || 'Brak dodatkowego opisu od użytkownika.';
  const [activeAttachmentPreview, setActiveAttachmentPreview] = useState<string | null>(null);

  return (
    <div className="admin-preview-overlay" role="dialog" aria-modal="true" aria-label="Szczegóły zgłoszenia" onClick={onClose}>
      <div className="admin-report-details-modal" onClick={(event) => event.stopPropagation()}>
        <button type="button" className="admin-preview-close" onClick={onClose} aria-label="Zamknij szczegóły zgłoszenia">×</button>

        <div className="admin-report-details-head">
          <h2>Szczegóły zgłoszenia #{report.id}</h2>
          <em className={`admin-status ${report.status === 'OPEN' ? 'pending' : 'approved'}`}>
            {ADMIN_REPORT_STATUS_LABELS[report.status]}
          </em>
        </div>

        <div className="admin-report-details-grid">
          <article>
            <span>Powód zgłoszenia</span>
            <strong>{report.reasonTitle}</strong>
            <small>ID powodu: {report.reasonId}</small>
          </article>
          <article>
            <span>Zgłaszający</span>
            <strong>{report.reporterName || report.reporterEmail}</strong>
            <small>{report.reporterEmail}</small>
          </article>
          <article>
            <span>Ogłoszenie</span>
            <strong>{report.listingTitle}</strong>
            <small>{report.listingCity} • ID: {report.listingId}</small>
          </article>
          <article>
            <span>Data zgłoszenia</span>
            <strong>{formatAdminDate(report.createdAt)}</strong>
            <small>{report.listingDeleted ? 'Ogłoszenie zostało usunięte.' : 'Ogłoszenie nadal aktywne w systemie.'}</small>
          </article>
        </div>

        <section className="admin-report-details-section">
          <h3>Opis od użytkownika</h3>
          <p>{details}</p>
        </section>

        <section className="admin-report-details-section">
          <h3>Załączniki</h3>
          {attachments.length === 0 && report.attachmentNames.length === 0 ? (
            <p className="admin-empty" style={{ padding: 0, textAlign: 'left' }}>Brak załączników.</p>
          ) : attachments.length === 0 ? (
            <div className="admin-report-attachments admin-report-attachments-fallback">
              {report.attachmentNames.map((name) => (
                <article key={name}>
                  <div className="admin-report-attachment-placeholder">
                    <Icon name="document" />
                  </div>
                  <strong>{name}</strong>
                  <small>Brak podglądu (starsze zgłoszenie)</small>
                </article>
              ))}
            </div>
          ) : (
            <div className="admin-report-attachments">
              {attachments.map((attachment, index) => {
                const imageAttachment = isImageAttachment(attachment) && Boolean(attachment.dataUrl);
                return (
                  <article key={`${attachment.fileName}-${index}`}>
                    {imageAttachment ? (
                      <button
                        type="button"
                        className="admin-report-attachment-preview"
                        onClick={() => setActiveAttachmentPreview(attachment.dataUrl ?? null)}
                        aria-label={`Powiększ załącznik ${attachment.fileName}`}
                      >
                        <img src={attachment.dataUrl ?? ''} alt={attachment.fileName} loading="lazy" />
                      </button>
                    ) : (
                      <div className="admin-report-attachment-placeholder">
                        <Icon name="document" />
                      </div>
                    )}
                    <strong>{attachment.fileName}</strong>
                    <small>{attachment.fileType || 'Nieznany typ pliku'}</small>
                  </article>
                );
              })}
            </div>
          )}
        </section>

        <div className="admin-preview-actions">
          {onPreviewListing && (
            <button type="button" className="admin-btn view" onClick={onPreviewListing}>
              Podgląd ogłoszenia
            </button>
          )}
          <button type="button" className="admin-btn" onClick={onClose}>Zamknij</button>
        </div>

        {activeAttachmentPreview && (
          <div
            className="admin-attachment-lightbox"
            role="dialog"
            aria-modal="true"
            aria-label="Powiększony podgląd załącznika"
            onClick={(event) => {
              event.stopPropagation();
              setActiveAttachmentPreview(null);
            }}
          >
            <div className="admin-attachment-lightbox-modal" onClick={(event) => event.stopPropagation()}>
              <button
                type="button"
                className="admin-attachment-lightbox-close"
                onClick={() => setActiveAttachmentPreview(null)}
                aria-label="Zamknij podgląd załącznika"
              >
                ×
              </button>
              <img src={activeAttachmentPreview} alt="Powiększony załącznik" />
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

function AdminListingPreviewModal({ listingId, busy, onClose, onApprove, onReject }: {
  listingId: number;
  busy: boolean;
  onClose: () => void;
  onApprove: () => void;
  onReject: () => void;
}) {
  const [listing, setListing] = useState<ApiListingDetail | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [activePhoto, setActivePhoto] = useState(0);

  useEffect(() => {
    let cancelled = false;
    setLoading(true);
    setError(null);
    setActivePhoto(0);
    apiFetch<ApiListingDetail>(`/listings/${listingId}`)
      .then((data) => { if (!cancelled) { setListing(data); setLoading(false); } })
      .catch((err) => { if (!cancelled) { setError(err instanceof Error ? err.message : 'Nie udało się wczytać ogłoszenia.'); setLoading(false); } });
    return () => { cancelled = true; };
  }, [listingId]);

  const gallery = listing && listing.photos.length > 0 ? listing.photos : [heroImage];

  const rows: Array<{ label: string; value: string }> = [];
  if (listing) {
    const pushRow = (label: string, value: string | number | null | undefined) => {
      if (value !== null && value !== undefined && `${value}`.trim() !== '') {
        rows.push({ label, value: `${value}` });
      }
    };
    const perMeter = listingPerMeter(listing.price, listing.area);
    pushRow('Typ oferty', OFFER_TYPE_LABELS[listing.offerType]);
    pushRow('Nieruchomość', PROPERTY_TYPE_LABELS[listing.propertyType]);
    pushRow('Rynek', listing.market);
    if (perMeter) pushRow('Cena za m²', perMeter);
    pushRow('Powierzchnia', `${`${listing.area}`.replace('.', ',')} m²`);
    pushRow('Liczba pokoi', listing.rooms);
    const floorText = listingFloorText(listing.floor, listing.buildingFloors);
    if (floorText) pushRow('Piętro', floorText);
    pushRow('Rok budowy', listing.yearBuilt);
    pushRow('Stan', listing.condition);
    pushRow('Forma własności', listing.ownership);
    pushRow('Typ budynku', listing.buildingType);
    pushRow('Materiał', listing.buildingMaterial);
    pushRow('Okna', listing.windows);
    pushRow('Ekspozycja', listing.exposure);
    pushRow('Wysokość', listing.roomHeight ? `${listing.roomHeight} m` : null);
    pushRow('Umeblowanie', listing.furnishing);
    pushRow('Dostępne od', listing.availableFrom);
    if (listing.offerType === 'RENT' && listing.rentExtra) pushRow('Czynsz', formatPln(listing.rentExtra));
  }

  return (
    <div className="admin-preview-overlay" role="dialog" aria-modal="true" aria-label="Podgląd ogłoszenia" onClick={onClose}>
      <div className="admin-preview-modal" onClick={(event) => event.stopPropagation()}>
        <button type="button" className="admin-preview-close" onClick={onClose} aria-label="Zamknij podgląd">×</button>

        {loading ? (
          <p className="admin-empty">Wczytywanie ogłoszenia…</p>
        ) : error || !listing ? (
          <p className="admin-empty">{error ?? 'Nie znaleziono ogłoszenia.'}</p>
        ) : (
          <>
            <div className="admin-preview-content">
              <div className="admin-preview-gallery">
                <div className="admin-preview-main" style={{ backgroundImage: `url(${gallery[activePhoto] ?? gallery[0]})` }} />
                {gallery.length > 1 && (
                  <div className="admin-preview-thumbs">
                    {gallery.map((photo, index) => (
                      <button
                        type="button"
                        key={index}
                        className={index === activePhoto ? 'active' : ''}
                        style={{ backgroundImage: `url(${photo})` }}
                        onClick={() => setActivePhoto(index)}
                        aria-label={`Zdjęcie ${index + 1}`}
                      />
                    ))}
                  </div>
                )}
              </div>

              <div className="admin-preview-info">
                <div className="admin-preview-head">
                  <h2>{listing.title}</h2>
                  <em className={`admin-status ${listing.status.toLowerCase()}`}>{LISTING_STATUS_LABELS[listing.status]}</em>
                </div>
                <p className="admin-preview-loc">
                  {[listing.city, listing.district, listing.street && listing.building ? `${listing.street} ${listing.building}` : listing.street].filter(Boolean).join(', ') || listing.city}
                </p>
                <div className="admin-preview-price">
                  <strong>{formatPln(listing.price)}{listing.offerType === 'RENT' ? ' /mc' : ''}</strong>
                </div>

                <div className="admin-preview-specs">
                  {rows.map((row) => (
                    <div key={row.label}><span>{row.label}</span><strong>{row.value}</strong></div>
                  ))}
                </div>

                <div className="admin-preview-desc">
                  <h3>Opis</h3>
                  <p>{listing.description}</p>
                </div>

                {(listing.media.length > 0 || listing.amenities.length > 0) && (
                  <div className="admin-preview-chips">
                    {[...listing.media, ...listing.amenities].map((item) => (<span key={item}>{item}</span>))}
                  </div>
                )}

                <div className="admin-preview-contact">
                  <strong>Kontakt:</strong> {listing.contactName || '—'} {listing.contactPhone ? `• ${listing.contactPhone}` : ''} {listing.contactEmail ? `• ${listing.contactEmail}` : ''}
                  <br /><strong>Autor:</strong> {listing.ownerEmail || '—'}
                </div>
              </div>
            </div>

            <div className="admin-preview-actions">
              {listing.status !== 'APPROVED' && (
                <button type="button" className="admin-btn approve" disabled={busy} onClick={onApprove}>Zatwierdź</button>
              )}
              {listing.status !== 'REJECTED' && (
                <button type="button" className="admin-btn reject" disabled={busy} onClick={onReject}>Odrzuć</button>
              )}
              <button type="button" className="admin-btn" onClick={onClose}>Zamknij</button>
            </div>
          </>
        )}
      </div>
    </div>
  );
}

function Icon({ name }: { name: string }) {
  switch (name) {
    case 'logo':
      return (
        <svg viewBox="0 0 28 28" aria-hidden="true">
          <path d="M4 13.2 14 5l10 8.2v9.3a1.5 1.5 0 0 1-1.5 1.5h-5.8v-7.2H11.3V24H5.5A1.5 1.5 0 0 1 4 22.5v-9.3Z" />
        </svg>
      );
    case 'heart':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M20.1 5.8a5.1 5.1 0 0 0-7.2 0l-.9.9-.9-.9a5.1 5.1 0 0 0-7.2 7.2L12 21l8.1-8a5.1 5.1 0 0 0 0-7.2Z" />
        </svg>
      );
    case 'heart-filled':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M12 21 3.9 13a5.1 5.1 0 0 1 7.2-7.2l.9.9.9-.9a5.1 5.1 0 1 1 7.2 7.2L12 21Z" />
        </svg>
      );
    case 'wifi':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M4 9a12 12 0 0 1 16 0M7.5 12.5a7 7 0 0 1 9 0M10.5 16a3 3 0 0 1 3 0M12 19h.1" />
        </svg>
      );
    case 'screen':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M4 5h16v12H4V5ZM9 21h6M12 17v4" />
        </svg>
      );
    case 'snow':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M12 3v18M5 7l14 10M19 7 5 17M8 4.5 12 8l4-3.5M8 19.5l4-3.5 4 3.5" />
        </svg>
      );
    case 'user':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M12 12.5a4.2 4.2 0 1 0 0-8.4 4.2 4.2 0 0 0 0 8.4Zm7.1 7.4a7.1 7.1 0 0 0-14.2 0" />
        </svg>
      );
    case 'search':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="m21 21-4.4-4.4m2.4-5.1a7.5 7.5 0 1 1-15 0 7.5 7.5 0 0 1 15 0Z" />
        </svg>
      );
    case 'pin':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M19 10.2c0 5-7 10.8-7 10.8s-7-5.8-7-10.8a7 7 0 1 1 14 0Z" />
          <path d="M12 12.6a2.4 2.4 0 1 0 0-4.8 2.4 2.4 0 0 0 0 4.8Z" />
        </svg>
      );
    case 'building':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M5 21V5.8L12 3l7 2.8V21M9 9h.1M9 13h.1M9 17h.1M15 9h.1M15 13h.1M15 17h.1M3 21h18" />
        </svg>
      );
    case 'chevron':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="m7 10 5 5 5-5" />
        </svg>
      );
    case 'spark':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M12 3 9.9 9.9 3 12l6.9 2.1L12 21l2.1-6.9L21 12l-6.9-2.1L12 3Z" />
        </svg>
      );
    case 'chart':
    case 'trend':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M4 19V5M4 19h16M8 15l3.2-3.4 3 2.2L20 7" />
          <path d="M17 7h3v3" />
        </svg>
      );
    case 'target':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M12 20a8 8 0 1 0 0-16 8 8 0 0 0 0 16Z" />
          <path d="M12 16a4 4 0 1 0 0-8 4 4 0 0 0 0 8ZM12 2v4M12 18v4M2 12h4M18 12h4" />
        </svg>
      );
    case 'shuffle':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M4 7h3.2c2.4 0 3.7 1.3 5.1 3.8l.9 1.6c1.3 2.4 2.8 3.6 5.2 3.6H20" />
          <path d="m17 13 3 3-3 3" />
          <path d="M4 17h3.2c1.8 0 3-.7 4.1-2.1" />
          <path d="M13.3 9.1c1.2-1.4 2.6-2.1 4.5-2.1H20" />
          <path d="m17 4 3 3-3 3" />
        </svg>
      );
    case 'shield':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M12 21s7-3.6 7-10V5.8L12 3 5 5.8V11c0 6.4 7 10 7 10Z" />
          <path d="m9.2 12 1.8 1.8 4-4" />
        </svg>
      );
    case 'gear':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M12 15.5a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7Z" />
          <path d="m19.4 15.2 1.1 1.9-2.3 2.3-1.9-1.1a8.5 8.5 0 0 1-2 .8l-.6 2.2h-3.4l-.6-2.2a8.5 8.5 0 0 1-2-.8l-1.9 1.1-2.3-2.3 1.1-1.9a8.5 8.5 0 0 1-.8-2l-2.2-.6V9.2l2.2-.6a8.5 8.5 0 0 1 .8-2L2.5 4.7l2.3-2.3 1.9 1.1a8.5 8.5 0 0 1 2-.8l.6-2.2h3.4l.6 2.2a8.5 8.5 0 0 1 2 .8l1.9-1.1 2.3 2.3-1.1 1.9a8.5 8.5 0 0 1 .8 2l2.2.6v3.4l-2.2.6a8.5 8.5 0 0 1-.8 2Z" />
        </svg>
      );
    case 'house':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M3 11.5 12 4l9 7.5V21h-6v-6H9v6H3v-9.5Z" />
        </svg>
      );
    case 'coin':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18Z" />
          <path d="M14.8 8.8c-.7-.6-1.6-.9-2.7-.9-1.5 0-2.7.7-2.7 1.9 0 1.3 1.5 1.7 2.9 2 1.4.3 2.7.7 2.7 2.1s-1.3 2.2-3 2.2c-1.2 0-2.3-.4-3.1-1.2M12 6.5v11" />
        </svg>
      );
    case 'arrow':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M5 12h14M13 6l6 6-6 6" />
        </svg>
      );
    case 'flame':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M12 22c4.4-1.2 7-4.2 7-8.2 0-3.5-2.1-5.9-5-8.8.1 2-.8 3.5-2.4 4.8C10.5 7.9 9.4 6.2 8 5c.3 3.2-3 5.3-3 9 0 3.9 2.8 6.8 7 8Z" />
        </svg>
      );
    case 'sun':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M12 17a5 5 0 1 0 0-10 5 5 0 0 0 0 10ZM12 1.8v2.4M12 19.8v2.4M4.8 4.8l1.7 1.7M17.5 17.5l1.7 1.7M1.8 12h2.4M19.8 12h2.4M4.8 19.2l1.7-1.7M17.5 6.5l1.7-1.7" />
        </svg>
      );
    case 'student':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="m3 8.5 9-4.5 9 4.5-9 4.5-9-4.5Z" />
          <path d="M7 11v4.2c0 1.6 2.2 2.8 5 2.8s5-1.2 5-2.8V11M21 8.5v5" />
        </svg>
      );
    case 'paw':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M8.6 10.5c1.1 0 2-1.2 2-2.8S9.7 4.9 8.6 4.9s-2 1.2-2 2.8.9 2.8 2 2.8ZM15.4 10.5c1.1 0 2-1.2 2-2.8s-.9-2.8-2-2.8-2 1.2-2 2.8.9 2.8 2 2.8ZM5.2 14.1c.9 0 1.7-1 1.7-2.2s-.8-2.2-1.7-2.2-1.7 1-1.7 2.2.8 2.2 1.7 2.2ZM18.8 14.1c.9 0 1.7-1 1.7-2.2s-.8-2.2-1.7-2.2-1.7 1-1.7 2.2.8 2.2 1.7 2.2Z" />
          <path d="M8 16.3c.6-1.8 2-3.2 4-3.2s3.4 1.4 4 3.2c.7 2.1-.8 3.7-2.5 2.9-.9-.4-2.1-.4-3 0-1.7.8-3.2-.8-2.5-2.9Z" />
        </svg>
      );
    case 'car':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M4 13.5 5.8 8c.3-.9 1.1-1.5 2-1.5h8.4c.9 0 1.7.6 2 1.5l1.8 5.5M3 13.5h18v5H3v-5Z" />
          <path d="M7 18.5v1M17 18.5v1M7.5 16h.1M16.5 16h.1" />
        </svg>
      );
    case 'bell':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M18 9a6 6 0 0 0-12 0c0 7-3 7-3 7h18s-3 0-3-7ZM13.7 21a2 2 0 0 1-3.4 0" />
        </svg>
      );
    case 'credit-card':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M3.5 6.5h17v11h-17v-11ZM3.5 10h17M7 15h3" />
        </svg>
      );
    case 'scale':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M12 3v18M5 7h14M7 7l-4 7h8L7 7ZM17 7l-4 7h8l-4-7ZM9 21h6" />
        </svg>
      );
    case 'paint':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M4 4h12v6H4V4ZM7 10v3h8v-3M11 13v7" />
          <path d="M9.5 20h3" />
        </svg>
      );
    case 'truck':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M3 6h11v9H3V6ZM14 9h4l3 3v3h-7V9Z" />
          <path d="M7 19a2 2 0 1 0 0-4 2 2 0 0 0 0 4ZM18 19a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z" />
        </svg>
      );
    case 'sofa':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M6 11V8a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v3M4.5 11.5A2.5 2.5 0 0 0 2 14v4h20v-4a2.5 2.5 0 0 0-4.9-.7H6.9a2.5 2.5 0 0 0-2.4-1.8ZM5 18v2M19 18v2" />
        </svg>
      );
    case 'camera':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M4 8h4l1.5-2h5L16 8h4v11H4V8Z" />
          <path d="M12 16a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7Z" />
        </svg>
      );
    case 'upload':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M7 18.5H6.4A4.4 4.4 0 0 1 6 9.7 6.2 6.2 0 0 1 18 8.4a4.8 4.8 0 0 1-.7 10.1H17" />
          <path d="M12 12v8M8.8 15.2 12 12l3.2 3.2" />
        </svg>
      );
    case 'city':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M4 21V7h6v14M10 21V3h10v18M2 21h20M7 11h.1M7 15h.1M14 7h.1M14 11h.1M14 15h.1M17 7h.1M17 11h.1M17 15h.1" />
        </svg>
      );
    case 'dots':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M5 12h.1M12 12h.1M19 12h.1" />
        </svg>
      );
    case 'dots-vertical':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M12 5h.1M12 12h.1M12 19h.1" />
        </svg>
      );
    case 'plus':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M12 5v14M5 12h14" />
        </svg>
      );
    case 'check':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="m5 12 4 4L19 6" />
        </svg>
      );
    case 'hammer':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="m14 5 5 5M12 7l5 5M4 20l8.5-8.5M14 5l2-2 5 5-2 2" />
        </svg>
      );
    case 'letter':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M6 20V4l12 16V4" />
        </svg>
      );
    case 'star':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="m12 3 2.7 5.5 6.1.9-4.4 4.3 1 6.1L12 17l-5.4 2.8 1-6.1-4.4-4.3 6.1-.9L12 3Z" />
        </svg>
      );
    case 'phone':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M22 16.9v3a2 2 0 0 1-2.2 2A19.8 19.8 0 0 1 3.1 5.2 2 2 0 0 1 5.1 3h3a2 2 0 0 1 2 1.7c.1 1 .4 2 .7 2.9a2 2 0 0 1-.4 2.1L9.2 10.9a16 16 0 0 0 3.9 3.9l1.2-1.2a2 2 0 0 1 2.1-.4c.9.3 1.9.6 2.9.7a2 2 0 0 1 1.7 2Z" />
        </svg>
      );
    case 'message':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M4 5h16v11H8l-4 4V5Z" />
        </svg>
      );
    case 'group':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M16 20a4 4 0 0 0-8 0M12 12a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7ZM20 20a3 3 0 0 0-3-3M17 5.4a3 3 0 0 1 0 5.2M4 20a3 3 0 0 1 3-3M7 5.4a3 3 0 0 0 0 5.2" />
        </svg>
      );
    case 'calendar':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M5 4h14v16H5V4ZM5 9h14M8 2v4M16 2v4" />
        </svg>
      );
    case 'document':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M7 3h7l4 4v14H7V3Z" />
          <path d="M14 3v5h5M10 13h6M10 17h4" />
        </svg>
      );
    case 'calculator':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M6 3h12v18H6V3ZM9 7h6M9 11h.1M12 11h.1M15 11h.1M9 15h.1M12 15h.1M15 15h.1M9 18h.1M12 18h.1M15 18h.1" />
        </svg>
      );
    case 'money':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M4 7h16v10H4V7ZM8 17a4 4 0 0 0-4-4M20 13a4 4 0 0 0-4 4M8 7a4 4 0 0 1-4 4M20 11a4 4 0 0 1-4-4" />
          <path d="M12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z" />
        </svg>
      );
    case 'warning':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M12 3 2.5 20h19L12 3Z" />
          <path d="M12 9v4M12 17h.1" />
        </svg>
      );
    case 'trash':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M4 7h16" />
          <path d="M9 7V5.5a1.5 1.5 0 0 1 1.5-1.5h3A1.5 1.5 0 0 1 15 5.5V7" />
          <path d="M7 7l.7 12.1A2 2 0 0 0 9.7 21h4.6a2 2 0 0 0 2-1.9L17 7" />
          <path d="M10 11v6M14 11v6" />
        </svg>
      );
    case 'mail':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M4 6h16v12H4V6Z" />
          <path d="m4 7 8 6 8-6" />
        </svg>
      );
    case 'tour':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M4 8a8 4 0 0 1 16 0c0 2.2-3.6 4-8 4S4 10.2 4 8Z" />
          <path d="M4 8v8c0 2.2 3.6 4 8 4s8-1.8 8-4V8M9 15l3-2 3 2" />
        </svg>
      );
    case 'lock':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M6 10h12v10H6V10ZM8.5 10V7.5a3.5 3.5 0 0 1 7 0V10" />
        </svg>
      );
    case 'clock':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18Z" />
          <path d="M12 7v5l3 2" />
        </svg>
      );
    case 'link':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M10 13a5 5 0 0 0 7.1 0l2-2a5 5 0 0 0-7.1-7.1l-1.1 1.1" />
          <path d="M14 11a5 5 0 0 0-7.1 0l-2 2A5 5 0 0 0 12 20.1l1.1-1.1" />
        </svg>
      );
    case 'percent':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M19 5 5 19M7.5 9.5a2 2 0 1 0 0-4 2 2 0 0 0 0 4ZM16.5 18.5a2 2 0 1 0 0-4 2 2 0 0 0 0 4Z" />
        </svg>
      );
    case 'key':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M14.5 9.5a4.5 4.5 0 1 0-3.2 4.3L13 15.5h2v2h2v2h3v-3.2l-5.5-5.5Z" />
          <path d="M7.5 10.5h.1" />
        </svg>
      );
    case 'eye':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M2.5 12s3.5-6 9.5-6 9.5 6 9.5 6-3.5 6-9.5 6-9.5-6-9.5-6Z" />
          <path d="M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z" />
        </svg>
      );
    case 'list':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M8 6h13M8 12h13M8 18h13M3 6h.1M3 12h.1M3 18h.1" />
        </svg>
      );
    case 'moon':
      return (
        <svg viewBox="0 0 24 24" aria-hidden="true">
          <path d="M19 14.5A8.5 8.5 0 1 1 9.5 5a7 7 0 0 0 9.5 9.5Z" />
        </svg>
      );
    default:
      return null;
  }
}

export default App;
