// Piesele comune ale panoului: celula si capul de tabel, capul sortabil si
// eticheta de camp din ferestre.
//
// Toate fisierele .jsx din panou impart un singur scope global, iar pana acum
// aceleasi patru piese erau scrise din nou in fiecare ecran. Consecinta era ca
// definitia din fisierul incarcat ultimul le castiga pe toate celelalte, deci o
// modificare facuta intr-un ecran nu se vedea nicaieri. De aici incolo exista
// un singur loc.
//
// Se declara cu `function`, nu cu `const`: doua `const` cu acelasi nume in doua
// fisiere opresc compilarea si lasa panoul alb pentru toata lumea. Din acelasi
// motiv nu se scrie nicaieri `const Td = window.Td;`.

// `className` trece mai departe ca sa se poata alinia capul de coloana la
// dreapta pe coloanele de cifre (.num-col). Fara el, cifrele stau la dreapta
// si titlul coloanei ramane la stanga — exact nepotrivirea despre care
// avertizeaza comentariul din styles.css.
function Th({ children, className }) {
  return <th className={className} style={{ textAlign: 'left', padding: '14px 16px', fontWeight: 600, fontSize: 11, color: 'var(--txt-3)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>{children}</th>;
}

// `max` (optional) taie textul prea lung cu trei puncte, in loc sa lateasca
// coloana peste tot tabelul: <Td max={220}>{c.note}</Td>. Cand continutul e
// text simplu se pune si `title`, ca omul sa vada intreg la hover.
// Fara `max` randeaza exact ca inainte — acelasi <td>, aceleasi doua proprietati
// de stil, niciun atribut in plus. E folosit in sute de locuri, deci asta e
// singura garantie care conteaza.
function Td({ children, max, className }) {
  const style = { padding: '14px 16px', color: 'var(--txt-1)' };
  if (max) {
    style.maxWidth = max;
    style.overflow = 'hidden';
    style.textOverflow = 'ellipsis';
    style.whiteSpace = 'nowrap';
  }
  const esteText = typeof children === 'string' || typeof children === 'number';
  return <td className={className} style={style} title={max && esteText ? String(children) : undefined}>{children}</td>;
}

function ThSortable({ children, sortKey, currentSort, currentDir, onSort, className }) {
  const isActive = sortKey && currentSort === sortKey;
  const clickable = !!sortKey;
  return (
    <th
      className={className}
      onClick={clickable ? () => onSort(sortKey) : undefined}
      // Sageata ↑ / ↓ spune vazatorului dupa ce e sortat tabelul; `aria-sort`
      // spune acelasi lucru cititorului de ecran, care nu vede sageata.
      // Se pune doar pe coloanele care chiar se pot sorta: pe una fara `sortKey`
      // ar anunta o sortare care nu exista.
      aria-sort={clickable ? (isActive ? (currentDir === 'asc' ? 'ascending' : 'descending') : 'none') : undefined}
      style={{
        textAlign: 'left', padding: '14px 16px', fontWeight: 600, fontSize: 11,
        color: isActive ? 'var(--accent-1)' : 'var(--txt-3)',
        textTransform: 'uppercase', letterSpacing: '0.05em',
        cursor: clickable ? 'pointer' : 'default',
        userSelect: 'none'
      }}
    >
      {children}
      {isActive && <span style={{ marginLeft: 6 }}>{currentDir === 'asc' ? '↑' : '↓'}</span>}
    </th>
  );
}

// `hint` e preluat din EditFieldBl (blacklist), singura varianta care il avea.
// Fara `hint` randeaza exact ca inainte, deci nu schimba niciunul din campurile
// existente.
function EditField({ label, children, hint }) {
  return (
    <div style={{ marginBottom: 16 }}>
      <label className="mono" style={{ fontSize: 10, color: 'var(--txt-3)', letterSpacing: '0.1em', marginBottom: 6, display: 'block' }}>{label.toUpperCase()}</label>
      {children}
      {hint && <div style={{ fontSize: 11, color: 'var(--txt-3)', marginTop: 5 }}>{hint}</div>}
    </div>
  );
}

// ---------------------------------------------------------------------------
// Utilitare mici, puse direct pe window ca sa nu ocupe nume in scope-ul global
// pe care un ecran l-ar putea folosi din intamplare pentru altceva.
// ---------------------------------------------------------------------------

// Mesaje de eroare pe intelesul omului.
//
// Regula de aur: NU traducem ce e deja scris pentru om. Triggerele noastre din
// SQL ridica exceptii scrise in romana (vezi sql/13-protectie-id-unic.sql), iar
// unele au cod de Postgres standard — daca ne-am uita doar la cod, am inlocui un
// mesaj bun, scris anume, cu unul generic.
(function () {
  // Diacriticele sunt dovada sigura, dar la noi se scrie romana fara ele, deci
  // avem nevoie si de a doua proba: cuvinte de legatura care nu apar in erorile
  // in engleza ale bazei de date. Cerem doua potriviri, ca sa nu declaram
  // "romaneasca" o eroare tehnica in care s-a nimerit o valoare in romana.
  const DIACRITICE = /[ăâîșşțţĂÂÎȘŞȚŢ]/;
  const CUVINTE_RO = /\b(nu|se|este|sunt|care|din|pentru|daca|poate|deja|trebuie|fara|sau|dupa|doar|mai|acest|aceasta|aceste|sa|cu|la|ca|si|ale|unui|unei|exista|folosit|sters|salvat|modificat|incearca|valoarea|campul)\b/gi;
  // Semnaturile unui mesaj brut de Postgres / PostgREST. Daca apare vreuna,
  // mesajul nu e pentru om oricat de romaneste ar suna restul.
  const TEHNIC = /(violates|constraint|duplicate key|row-level security|permission denied|relation "|column "|table "|invalid input syntax|does not exist|null value in column|syntax error|JWT|Failed to fetch|NetworkError|Load failed)/i;

  const PE_COD = {
    '23505': 'Exista deja o inregistrare cu aceste date. Verifica daca nu ai adaugat-o o data.',
    '23503': 'Inregistrarea e legata de altceva si nu poate fi stearsa sau mutata asa.',
    '23502': 'Un camp obligatoriu a ramas gol.',
    '23514': 'Datele nu trec regulile de validare ale bazei.',
    '22P02': 'O valoare e scrisa gresit — probabil o cifra sau o data.',
    '22001': 'Textul e prea lung pentru campul asta.',
    '42501': 'Nu ai drepturi pentru actiunea asta. Cere-i lui Vlad sa verifice.',
    'PGRST301': 'Sesiunea a expirat. Reincarca pagina si autentifica-te din nou.',
    'PGRST116': 'Nu am gasit inregistrarea — poate a fost stearsa intre timp.',
  };

  window.mesajEroare = function (error) {
    // Detaliul brut ramane in consola: acolo e util, in interfata nu.
    console.error('[4U]', error);
    if (!error) return 'A aparut o eroare. Incearca din nou.';

    const brut = String(error.message || error.error_description || error.msg || error || '').trim();
    const cod = String(error.code || error.statusCode || '');

    if (brut) {
      const tehnic = TEHNIC.test(brut);
      if (!tehnic) {
        if (DIACRITICE.test(brut)) return brut;
        const potriviri = brut.match(CUVINTE_RO);
        if (potriviri && potriviri.length >= 2) return brut;
      }
    }

    if (PE_COD[cod]) return PE_COD[cod];

    if (/Failed to fetch|NetworkError|Load failed|ERR_INTERNET/i.test(brut)) {
      return 'Nu am putut ajunge la server. Verifica internetul si incearca din nou.';
    }
    if (/JWT|token|not authenticated/i.test(brut)) {
      return 'Sesiunea a expirat. Reincarca pagina si autentifica-te din nou.';
    }

    return 'Nu am putut salva. Incearca din nou, iar daca se repeta trimite-i lui Vlad ora exacta.';
  };
})();

// Data si ora fara secunde — pentru tot ce citeste un om in fluxul de lucru.
window.fmtDateTime = function (v) {
  if (!v) return '—';
  const d = new Date(v);
  if (isNaN(d.getTime())) return '—';
  return d.toLocaleString('ro-RO', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' });
};

// Cu secunde — doar pentru jurnale si semnaturi, unde secunda e proba.
window.fmtStamp = function (v) {
  if (!v) return '—';
  const d = new Date(v);
  if (isNaN(d.getTime())) return '—';
  return d.toLocaleString('ro-RO', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' });
};

// ---------------------------------------------------------------------------
// window.useEscape(fn) — Escape inchide fereastra deschisa.
//
// Azi niciun modal din panou nu se inchide de la tastatura: singura iesire e
// x-ul din colt sau clicul pe fundal. Hook-ul asta e partea de tastatura.
//
// Se cheama NECONDITIONAT in corpul componentei, ca orice hook React:
//
//   function FereastraCreator({ onClose }) {
//     window.useEscape(onClose);
//     ...
//   }
//
// Daca fereastra se randeaza si cand e inchisa, trimite `null` in loc sa sari
// peste apel — hook-ul trebuie chemat la fiecare randare, dar accepta linistit
// o valoare goala si atunci nu face nimic:
//
//   window.useEscape(deschis ? inchide : null);
//
// Doar fereastra montata ULTIMA raspunde la Escape. Conteaza pentru ca
// ConfirmDialog se deschide peste un drawer: fara asta, un singur Escape ar
// inchide si confirmarea, si drawer-ul din spate cu cele 40 de campuri
// completate. Ordinea de montare tine loc de ordine vizuala — dialogul de
// deasupra se monteaza mereu dupa cel de dedesubt.
(function () {
  // Stiva de ascultatori. Doar ultimul primeste tasta.
  const stiva = [];

  function laTasta(e) {
    if (e.key !== 'Escape' && e.key !== 'Esc') return;
    // In timpul scrierii cu IME, Escape anuleaza compozitia, nu fereastra.
    if (e.isComposing || e.keyCode === 229) return;
    const varf = stiva[stiva.length - 1];
    if (!varf) return;
    const fn = varf.current;
    if (typeof fn === 'function') fn();
  }

  window.useEscape = function (fn) {
    // Functia sta intr-un ref, nu in dependentele efectului: altfel un
    // `onClose` scris inline (o sageata noua la fiecare randare) ar dezabona si
    // reabona ascultatorul la fiecare tastare intr-un input.
    const ref = React.useRef(fn);
    ref.current = fn;

    React.useEffect(() => {
      stiva.push(ref);
      if (stiva.length === 1) document.addEventListener('keydown', laTasta);
      return () => {
        const i = stiva.indexOf(ref);
        if (i !== -1) stiva.splice(i, 1);
        if (stiva.length === 0) document.removeEventListener('keydown', laTasta);
      };
    }, []);
  };
})();

// ---------------------------------------------------------------------------
// window.dinFundal(onClose) — clicul pe fundal, reparat.
//
// Pana acum wrap-ul avea `onClick={onClose}` simplu, iar asta stergea pana la 49
// de campuri completate dintr-un singur gest nevinovat: selectezi text intr-un
// textarea, tragi mouse-ul peste marginea ferestrei si dai drumul pe negru.
// Butonul se ridica in afara modalului, browserul urca pana la primul parinte
// comun — wrap-ul — si trimite acolo un `click` adevarat. Fereastra se inchide.
//
// Reparatia nu e `stopPropagation` pe modal (aia rupe orice alt click care ar
// trebui sa urce). Se verifica AMANDOUA capetele gestului: si apasarea, si
// ridicarea trebuie sa fi fost chiar pe fundal.
//
//   function FereastraCreator({ onClose }) {
//     const fundal = window.dinFundal(onClose);
//     return (
//       <div {...fundal} className="ma-modal-wrap">
//         <div className="ma-modal" onClick={(e) => e.stopPropagation()}> … </div>
//       </div>
//     );
//   }
//
// Se cheama tot neconditionat in corpul componentei (foloseste un ref). Doua
// ferestre in acelasi ecran inseamna doua apeluri, fiecare cu memoria lui — e in
// regula atat timp cat ambele apeluri se fac la fiecare randare, in aceeasi
// ordine.
window.dinFundal = function (onClose) {
  const peFundal = React.useRef(false);
  return {
    onMouseDown: function (e) {
      // `currentTarget` e wrap-ul, `target` e ce a fost apasat efectiv.
      peFundal.current = e.target === e.currentTarget;
    },
    onClick: function (e) {
      const ambele = peFundal.current && e.target === e.currentTarget;
      // Se sterge intotdeauna, si cand nu inchidem: altfel o apasare pe fundal
      // urmata de un click venit de altundeva ar inchide fereastra mai tarziu.
      peFundal.current = false;
      if (ambele && typeof onClose === 'function') onClose();
    }
  };
};

// ---------------------------------------------------------------------------
// window.incarcaProfil(session) — cine e omul logat, fara sa-l dea afara degeaba.
//
// In sapte ecrane era copiat acelasi bloc:
//
//   if (error || !prof || !prof.is_active) { await sb.auth.signOut(); … }
//
// `error` inseamna aproape mereu „internetul a clipit” sau „serverul a raspuns
// greu”. Pus in aceeasi conditie cu „nu are drept de acces”, transforma o
// pierdere de retea de doua secunde intr-o delogare adevarata: omul trebuie sa
// retasteze parola si pierde formularul deschis. Helperul separa cele doua.
//
// Returneaza una din trei:
//   { prof }                          — totul e in regula
//   { prof: null, eroareRetea: mesaj } — NU deloga; arata mesajul si lasa un „Reincearca”
//   { prof: null, deconecteaza: true } — chiar nu are ce cauta aici (cont sters / dezactivat)
//
// Asa se cheama:
//
//   const { data: { session } } = await window.sb.auth.getSession();
//   if (!session) { onNav('media-login'); return; }
//   const r = await window.incarcaProfil(session);
//   if (r.eroareRetea) { setEroare(r.eroareRetea); setLoading(false); return; }
//   if (r.deconecteaza) { await window.sb.auth.signOut(); onNav('media-login'); return; }
//   setProfile(r.prof);
//
// signOut-ul ramane in ecran, nu aici: helperul spune ce s-a intamplat, ecranul
// decide ce face cu propriul `onNav`.
//
// `coloane` e optional — implicit exact setul folosit de cele sapte ecrane.
// Ecranul de contracte, care cere '*', poate trimite '*'.
window.incarcaProfil = async function (session, coloane) {
  if (!window.sb) return { prof: null, deconecteaza: true };
  if (!session || !session.user || !session.user.id) return { prof: null, deconecteaza: true };

  let prof = null;
  let error = null;
  try {
    const r = await window.sb
      .from('staff_profiles')
      .select(coloane || 'id, full_name, email, role, is_active')
      .eq('id', session.user.id)
      .maybeSingle();
    prof = r.data;
    error = r.error;
  } catch (e) {
    // `fetch` picat de tot (offline) arunca, nu returneaza `error`.
    error = e;
  }

  if (error) return { prof: null, eroareRetea: window.mesajEroare(error) };
  if (!prof || !prof.is_active) return { prof: null, deconecteaza: true };
  return { prof };
};

// ---------------------------------------------------------------------------
// Plata unui creator pe o campanie.
//
// Doua feluri, cu consecinte fiscale diferite:
//
//   „Drepturi de autor" — creator persoana fizica. Agentia retine impozitul la
//   sursa si il vireaza ea la stat. Suma negociata e BRUTA, deci creatorul
//   incaseaza mai putin decat numarul despre care ati vorbit.
//
//   „Factura" — creator cu PFA sau SRL. Ne emite factura, o platim intreaga,
//   el isi plateste singur taxele. Retinere zero.
//
// Formula sta AICI si numai aici. Aceleasi cifre ajung in cardul creatorului,
// in totalul campaniei si in raport — daca ar fi calculate in trei locuri, s-ar
// desincroniza la prima modificare si nimeni n-ar observa pana la contabilitate.
//
//   window.calculPlata({ payment_amount: 1000, payment_type: 'Drepturi de autor', withholding_rate: 6 })
//   → { brut: 1000, retinere: 60, net: 940, areSuma: true }
window.calculPlata = function (alocare) {
  const a = alocare || {};
  // `String(...).replace` acopera virgula zecimala: campurile sunt type="number",
  // deci de obicei nu ajunge aici, dar valoarea vine si direct din starea unui
  // input in timp ce se tasteaza. Fara asta, „1000,50" ar fi citit ca „nicio suma"
  // si defalcarea ar disparea de sub ochii omului exact cand scrie.
  const brut = Number(String(a.payment_amount).replace(',', '.'));
  // Suma necompletata nu e acelasi lucru cu zero: una inseamna „inca nu stim",
  // cealalta „e pe gratis". Totalurile trebuie sa le poata deosebi.
  if (a.payment_amount == null || a.payment_amount === '' || !isFinite(brut)) {
    return { brut: 0, retinere: 0, net: 0, areSuma: false };
  }
  const peFactura = a.payment_type === 'Factură';
  const procent = peFactura ? 0 : Number(a.withholding_rate == null ? 6 : a.withholding_rate);
  const retinere = peFactura ? 0 : Math.round(brut * procent) / 100;
  // Net calculat prin scadere, nu prin inmultire cu (100 - procent): asa brutul
  // ramane mereu exact suma dintre retinere si net, fara un ban pierdut la
  // rotunjire. Contabilitatea verifica tocmai egalitatea asta.
  return { brut: brut, retinere: retinere, net: brut - retinere, areSuma: true };
};

// Cum se plateste un creator NU se alege de mana la plata: se citeste din
// contractul semnat cu el. Altfel cel care face platile ar trebui sa tina minte
// ce contract avem cu fiecare din cateva sute de creatori, iar un clic gresit
// inseamna ori ca nu retinem impozitul cand trebuia, ori ca il retinem cand nu
// trebuia — amandoua se repara greu, dupa ce banii au plecat.
//
// Ordinea surselor, de la cea mai sigura la cea mai slaba:
//   1. contractul semnat, daca stie ce fel e (contracte generate dupa sql/16)
//   2. contractul semnat inainte de sql/16, care nu stie — atunci spunem ca nu stim
//   3. niciun contract — cadem pe statutul din fisa creatorului, si o spunem
//
// Nu inventam niciodata un raspuns tacut: cand sursa e slaba, panoul scrie de ce.
//
//   window.felulPlatii(creator, contract)
//   → { tip, sursa: 'contract' | 'statut' | 'necunoscut', explicatie, sigur }
window.felulPlatii = function (creator, contract) {
  const statut = creator && creator.entity_type;
  const peFacturaDupaStatut = statut === 'PFA' || statut === 'SRL';

  if (contract && contract.payment_kind) {
    return {
      tip: contract.payment_kind,
      sursa: 'contract',
      sigur: true,
      explicatie: 'Conform contractului „' + (contract.contract_title || 'semnat') + '".',
    };
  }

  if (contract) {
    // Contract semnat, dar generat inainte sa existe campul. Statutul e cea mai
    // buna aproximatie, dar nu e acelasi lucru cu a sti.
    return {
      tip: peFacturaDupaStatut ? 'Factură' : 'Drepturi de autor',
      sursa: 'necunoscut',
      sigur: false,
      explicatie: 'Contractul „' + (contract.contract_title || 'semnat') + '" e mai vechi și nu spune ce fel e. '
        + 'Am luat după statutul din fișa creatorului (' + (statut || 'nespecificat') + ').',
    };
  }

  return {
    tip: peFacturaDupaStatut ? 'Factură' : 'Drepturi de autor',
    sursa: 'statut',
    sigur: false,
    explicatie: 'Nu există contract semnat cu el în panou. Am luat după statutul din fișa lui ('
      + (statut || 'nespecificat') + ').',
  };
};

// Cele trei stari in care poate fi plata unui creator, pentru afisare.
// NU se salveaza in baza: `payment_status` are un CHECK care accepta doar
// „Neplătit" si „Plătit", iar o a treia valoare ar fi respinsa la scriere.
// „Așteptăm factura" se deduce: e pe factura si inca n-a trimis-o.
window.starePlata = function (alocare) {
  const a = alocare || {};
  if (a.payment_status === 'Plătit') return { text: 'Plătit', ton: 'done' };
  if (a.payment_type === 'Factură' && !a.invoice_number) return { text: 'Așteptăm factura', ton: 'work' };
  if (a.payment_amount == null || a.payment_amount === '') return { text: 'Fără sumă', ton: 'idle' };
  return { text: 'De plătit', ton: 'wait' };
};

// 1250.5 → „1.250,50 RON". Pentru bani se arata mereu doi bani, chiar si la sume
// rotunde: o coloana in care unele randuri au zecimale si altele nu se citeste greu.
window.fmtBani = function (v) {
  const n = Number(v);
  if (v == null || v === '' || !isFinite(n)) return '—';
  return n.toLocaleString('ro-RO', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' RON';
};
