// ============================================================
// data.jsx — dane lokalizacji, agregaty głosów, store (localStorage)
// Dane koncepcyjne: praca końcowa WAT, case study Krosno.
// UWAGA (prototyp): nazwy ulic i współrzędne są ROBOCZE — do podmiany
// na rzeczywiste lokalizacje z koncepcji. Wartości MS/mp pochodzą
// z mapy koncepcji (suma MS = 7 550, suma mp = 505).
// ============================================================

const KROSNO = { name: "Krosno", teryt: "1861011", voivodeship: "podkarpackie", center: [21.7656, 49.6925], population: 43594, needTotal: 21797, needShelters: 10899 };

// 12 lokalizacji koncepcyjnych z mapy pracy WAT (source=admin, status=published)
// (spec mówi o 13 — na mapie koncepcji oznaczono 12 lokalizacji sumujących się do 7 550 MS)
const CONCEPT_LOCATIONS = [
  { slug: "parking-rzeszowska",      name: "Parking przy ul. Rzeszowskiej",        lat: 49.7128, lng: 21.7409, ms: 450,  mp: 30,  landCat: "parking", desc: "Istniejący parking naziemny w północno-zachodniej części miasta. Koncepcja: parking podziemny z funkcją schronu, na powierzchni odtworzenie zieleni osiedlowej." },
  { slug: "parking-markiewicza",     name: "Parking os. Markiewicza",              lat: 49.7016, lng: 21.7269, ms: 300,  mp: 25,  landCat: "parking", desc: "Parking osiedlowy przy ul. Krakowskiej. Zasięg dojścia obejmuje zwartą zabudowę wielorodzinną." },
  { slug: "parking-podkarpacka",     name: "Parking przy ul. Podkarpackiej",       lat: 49.6968, lng: 21.7368, ms: 250,  mp: 20,  landCat: "parking", desc: "Parking naziemny przy ciągu usługowym. Lokalizacja w siatce heksagonalnej 500 m pokrywa lukę zachodniej części śródmieścia." },
  { slug: "parking-czajkowskiego",   name: "Parking przy ul. Czajkowskiego",       lat: 49.7062, lng: 21.7654, ms: 250,  mp: 20,  landCat: "parking", desc: "Parking w północnej części miasta (rejon Korony). Obsługuje zabudowę jednorodzinną i usługi." },
  { slug: "parking-podwale",         name: "Parking Podwale",                      lat: 49.6951, lng: 21.7588, ms: 600,  mp: 40,  landCat: "parking", desc: "Duży parking naziemny w sąsiedztwie centrum. Wysoka gęstość zaludnienia w zasięgu dojścia 500 m." },
  { slug: "parking-grodzka",         name: "Parking przy ul. Grodzkiej",           lat: 49.6918, lng: 21.7672, ms: 1000, mp: 65,  landCat: "parking", desc: "Lokalizacja śródmiejska o najwyższym potencjale pokrycia zapotrzebowania. Koncepcja zakłada dwukondygnacyjny obiekt podziemny." },
  { slug: "parking-tysiaclecia",     name: "Parking os. Tysiąclecia",              lat: 49.6953, lng: 21.7752, ms: 1000, mp: 65,  landCat: "parking", desc: "Parking osiedlowy na os. Tysiąclecia. Gęsta zabudowa wielorodzinna; odzysk terenu na zieleń rekreacyjną dla mieszkańców." },
  { slug: "parking-wojska-polskiego",name: "Parking przy ul. Wojska Polskiego",    lat: 49.6877, lng: 21.7807, ms: 800,  mp: 50,  landCat: "parking", desc: "Parking naziemny przy głównym ciągu komunikacyjnym wschodniej części miasta." },
  { slug: "parking-traugutta",       name: "Parking os. Traugutta",                lat: 49.6831, lng: 21.7866, ms: 1600, mp: 100, landCat: "parking", desc: "Największa lokalizacja koncepcji — MS 1600 / mp 100. Obsługuje największe osiedle mieszkaniowe Krosna." },
  { slug: "parking-lwowska",         name: "Parking przy ul. Lwowskiej",           lat: 49.6770, lng: 21.7798, ms: 800,  mp: 50,  landCat: "parking", desc: "Parking przy ul. Lwowskiej w południowo-wschodniej części miasta." },
  { slug: "parking-zwirki-wigury",   name: "Parking przy ul. Żwirki i Wigury",     lat: 49.6857, lng: 21.7466, ms: 250,  mp: 20,  landCat: "parking", desc: "Parking w rejonie lotniska. Uzupełnia pokrycie siatki w południowo-zachodniej części miasta." },
  { slug: "parking-suchodolska",     name: "Parking przy ul. Suchodolskiej",       lat: 49.6789, lng: 21.7613, ms: 250,  mp: 20,  landCat: "parking", desc: "Parking naziemny obsługujący południową część miasta." },
].map((l, i) => ({
  id: "C" + (i + 1), source: "admin", status: "published", commune: "Krosno",
  addressLabel: l.name.replace("Parking przy ", "").replace("Parking ", "") + ", Krosno",
  images: [], createdAt: "2026-04-10", ...l,
}));

// Seedowane propozycje mieszkańców
const SEED_PROPOSALS = [
  {
    id: "U1", slug: "nieuzytek-okrzei", source: "user", status: "published",
    name: "Nieużytek przy ul. Okrzei", commune: "Krosno",
    lat: 49.6900, lng: 21.7530, landCat: "wasteland",
    addressLabel: "ul. Okrzei, Krosno",
    desc: "Od lat niezagospodarowany teren między blokami a szkołą. Idealne miejsce: blisko osiedla, brakuje tu miejsc parkingowych, a na górze mógłby powstać skwer z placem zabaw.",
    createdAt: "2026-05-21",
  },
  {
    id: "U2", slug: "plac-targowy-sanok", source: "user", status: "published",
    name: "Plac przy targowisku", commune: "Sanok",
    lat: 49.5582, lng: 22.2050, landCat: "square",
    addressLabel: "rejon targowiska, Sanok",
    desc: "Duży utwardzony plac wykorzystywany tylko w dni targowe. Parking podziemny rozwiązałby problem parkowania w centrum, a schron objąłby zasięgiem stare miasto.",
    createdAt: "2026-05-28",
  },
  {
    id: "U3", slug: "parking-iwonicz", source: "user", status: "pending",
    name: "Parking przy sanatorium", commune: "Iwonicz-Zdrój",
    lat: 49.5705, lng: 21.7890, landCat: "parking",
    addressLabel: "Iwonicz-Zdrój, rejon uzdrowiska",
    desc: "Parking obsługujący ruch uzdrowiskowy, w sezonie przepełniony. Funkcja ochronna objęłaby kuracjuszy i mieszkańców centrum.",
    createdAt: "2026-06-07",
  },
  {
    id: "U4", slug: "parking-grodzka-2", source: "user", status: "pending",
    name: "Parking koło Rynku", commune: "Krosno",
    lat: 49.6921, lng: 21.7683, landCat: "parking",
    addressLabel: "rejon Rynku, Krosno",
    desc: "Parking blisko Rynku — zawsze pełny. Pod ziemią zmieściłoby się dużo więcej aut, a schron byłby w samym centrum miasta.",
    createdAt: "2026-06-09",
  },
];

// Seedowane agregaty głosów (symulacja dotychczasowego ruchu)
const SEED_VOTES = {
  C1:  { buy: 9,  rent: 14, support: 21,  verified: 11 },
  C2:  { buy: 6,  rent: 9,  support: 14,  verified: 7 },
  C3:  { buy: 4,  rent: 7,  support: 11,  verified: 5 },
  C4:  { buy: 3,  rent: 6,  support: 12,  verified: 4 },
  C5:  { buy: 16, rent: 21, support: 30,  verified: 17 },
  C6:  { buy: 24, rent: 31, support: 44,  verified: 26 },
  C7:  { buy: 28, rent: 36, support: 41,  verified: 29 },
  C8:  { buy: 14, rent: 19, support: 26,  verified: 13 },
  C9:  { buy: 39, rent: 47, support: 58,  verified: 38 },
  C10: { buy: 13, rent: 18, support: 24,  verified: 12 },
  C11: { buy: 3,  rent: 5,  support: 9,   verified: 3 },
  C12: { buy: 4,  rent: 6,  support: 10,  verified: 4 },
  U1:  { buy: 8,  rent: 11, support: 27,  verified: 9 },
  U2:  { buy: 5,  rent: 7,  support: 19,  verified: 4 },
};

// ---------- Store (localStorage) ----------
const STORE_KEY = "sb-platforma-v1";

function loadStore() {
  try { return JSON.parse(localStorage.getItem(STORE_KEY)) || {}; } catch (e) { return {}; }
}
const emptyStore = () => ({
  myVotes: [],        // {locId, interest, priceBand, verified, ts}
  myProposals: [],    // pełne obiekty lokalizacji (status: pending)
  leads: [],          // {type, organization, contact, email, message, page, ts}
  moderated: {},      // id -> {action:'approved'|'rejected'|'merged', reason?, target?}
  adminLocations: [], // lokalizacje dodane z panelu admina
  archived: [],       // id-ki zarchiwizowane
  audit: [],          // {actor, action, entity, meta, ts}
});

let _store = { ...emptyStore(), ...loadStore() };
const _subs = new Set();

const Store = {
  get: () => _store,
  set(patch) {
    _store = { ..._store, ...patch };
    try { localStorage.setItem(STORE_KEY, JSON.stringify(_store)); } catch (e) {}
    _subs.forEach((fn) => fn(_store));
  },
  subscribe(fn) { _subs.add(fn); return () => _subs.delete(fn); },
  audit(actor, action, entity, meta) {
    Store.set({ audit: [{ actor, action, entity, meta: meta || null, ts: Date.now() }, ..._store.audit].slice(0, 200) });
  },
  reset() { _store = emptyStore(); try { localStorage.setItem(STORE_KEY, JSON.stringify(_store)); } catch (e) {} _subs.forEach((fn) => fn(_store)); },
};

function useStore() {
  const [s, setS] = React.useState(_store);
  React.useEffect(() => Store.subscribe(setS), []);
  return s;
}

// ---------- Selektory ----------
function allLocations() {
  const s = Store.get();
  const moderatedSeeds = SEED_PROPOSALS.map((p) => {
    const m = s.moderated[p.id];
    if (!m) return p;
    if (m.action === "approved") return { ...p, status: "published" };
    if (m.action === "rejected") return { ...p, status: "rejected", rejectionReason: m.reason };
    if (m.action === "merged") return { ...p, status: "merged", mergedInto: m.target };
    return p;
  });
  const mine = s.myProposals.map((p) => {
    const m = s.moderated[p.id];
    if (!m) return p;
    if (m.action === "approved") return { ...p, status: "published" };
    if (m.action === "rejected") return { ...p, status: "rejected", rejectionReason: m.reason };
    if (m.action === "merged") return { ...p, status: "merged", mergedInto: m.target };
    return p;
  });
  return [...CONCEPT_LOCATIONS, ...moderatedSeeds, ...mine, ...s.adminLocations]
    .filter((l) => !s.archived.includes(l.id));
}
const publishedLocations = () => allLocations().filter((l) => l.status === "published");
const pendingLocations = () => allLocations().filter((l) => l.status === "pending");
const locBySlug = (slug) => allLocations().find((l) => l.slug === slug);
const locById = (id) => allLocations().find((l) => l.id === id);

// agregaty głosów lokalizacji (seed + głosy użytkownika + przepięte przy merge)
function votesFor(locId) {
  const s = Store.get();
  const base = { buy: 0, rent: 0, support: 0, verified: 0, ...(SEED_VOTES[locId] || {}) };
  // głosy lokalizacji scalonych w tę lokalizację
  Object.entries(s.moderated).forEach(([srcId, m]) => {
    if (m.action === "merged" && m.target === locId) {
      const sv = SEED_VOTES[srcId];
      if (sv) { base.buy += sv.buy; base.rent += sv.rent; base.support += sv.support; base.verified += sv.verified; }
    }
  });
  s.myVotes.filter((v) => v.locId === locId && !v.withdrawn).forEach((v) => {
    base[v.interest] += 1;
    if (v.verified) base.verified += 1;
  });
  const total = base.buy + base.rent + base.support;
  return { ...base, total };
}

function platformTotals() {
  const locs = publishedLocations();
  let total = 0, verified = 0;
  locs.forEach((l) => { const v = votesFor(l.id); total += v.total; verified += v.verified; });
  return { locations: locs.length, total, verified };
}

function communeAggregates() {
  const map = {};
  publishedLocations().forEach((l) => {
    const v = votesFor(l.id);
    if (!map[l.commune]) map[l.commune] = { commune: l.commune, locations: 0, total: 0, verified: 0 };
    map[l.commune].locations += 1;
    map[l.commune].total += v.total;
    map[l.commune].verified += v.verified;
  });
  return Object.values(map).sort((a, b) => b.total - a.total);
}

// ---------- Geo helpers ----------
function haversine(lat1, lng1, lat2, lng2) {
  const R = 6371000, rad = Math.PI / 180;
  const dLat = (lat2 - lat1) * rad, dLng = (lng2 - lng1) * rad;
  const a = Math.sin(dLat / 2) ** 2 + Math.cos(lat1 * rad) * Math.cos(lat2 * rad) * Math.sin(dLng / 2) ** 2;
  return 2 * R * Math.asin(Math.sqrt(a));
}
function nearbyLocations(lat, lng, radiusM, excludeId) {
  return allLocations()
    .filter((l) => ["published", "pending"].includes(l.status) && l.id !== excludeId)
    .map((l) => ({ ...l, dist: haversine(lat, lng, l.lat, l.lng) }))
    .filter((l) => l.dist <= radiusM)
    .sort((a, b) => a.dist - b.dist);
}

const fmt = (n) => (n || 0).toLocaleString("pl-PL");
const slugify = (s) => s.toLowerCase().replace(/[ąćęłńóśźż]/g, (c) => ({ ą: "a", ć: "c", ę: "e", ł: "l", ń: "n", ó: "o", ś: "s", ź: "z", ż: "z" }[c])).replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");

const LAND_CATS = { parking: "parking naziemny", wasteland: "nieużytek", square: "plac", other: "inne" };
const INTERESTS = { buy: "zainteresowani kupnem", rent: "zainteresowani najmem", support: "popierający lokalizację" };

Object.assign(window, {
  KROSNO, CONCEPT_LOCATIONS, SEED_PROPOSALS, Store, useStore,
  allLocations, publishedLocations, pendingLocations, locBySlug, locById,
  votesFor, platformTotals, communeAggregates,
  haversine, nearbyLocations, fmt, slugify, LAND_CATS, INTERESTS,
});
