/* ===== Pago na Hora — App root ===== */

// Maps an API Entity (snake_case, amount_cents) into the provider shape the
// PJ views already consume. Kept here so every view shares one mapping.
function entityToProvider(e) {
  return {
    id: e.id,
    name: e.name,
    role: e.role || '',
    kind: (e.kind || '').toUpperCase(),
    doc: e.doc || '',
    pixType: e.pix_type || '',
    pixKey: e.pix_key || '',
    contract: e.contract_type || '',
    monthly: PNH.toReais(e.default_amount_cents),
    status: e.status || 'rascunho',
    city: e.city || '',
    since: e.since || '',
    email: e.email || '',
  };
}

// Maps an API Payment into the feed shape used by the PJ payment tables.
function paymentToFeed(p, entitiesById) {
  const ent = entitiesById && entitiesById[p.entity_id];
  return {
    id: p.id,
    who: (ent && ent.name) || p.payer_name || p.description || 'Entidade',
    kind: (ent && (ent.kind || '').toUpperCase()) || 'PF',
    desc: p.description || '',
    amount: PNH.toReais(p.amount_cents),
    status: p.status || 'pendente',
    date: p.scheduled_for || p.paid_at || '—',
    method: p.method === 'external' ? 'Externo' : 'Pix' + (ent && ent.pix_type ? ' · ' + ent.pix_type : ''),
    entity_id: p.entity_id,
  };
}

// Maps an API Receivable into the PF receivable row shape.
function receivableToRow(r) {
  return {
    id: r.id,
    from: r.payer_name || 'Pagador',
    logo: PNH.colorFor(r.payer_name || 'Pagador'),
    desc: r.description || '',
    amount: PNH.toReais(r.amount_cents),
    status: r.status || 'agendado',
    date: r.scheduled_for || r.paid_at || '—',
  };
}

// Shareable pt-BR routes: each app section maps to a URL hash so links can be
// shared and survive reload. Source of truth is the hash; nav updates it.
const SECTION_TO_HASH = {
  overview: 'painel',
  assistant: 'assistente',
  payments: 'pagar',
  receivables: 'receber',
  providers: 'entidades',
  'new-entity': 'entidades/nova',
  'new-account': 'conta/nova',
  contracts: 'contratos',
  documents: 'documentos',
  'new-contract': 'contratos/novo',
};
const HASH_TO_SECTION = Object.fromEntries(Object.entries(SECTION_TO_HASH).map(([k, v]) => [v, k]));
const sectionForHash = (raw) => HASH_TO_SECTION[(raw || '').replace('#', '').split('?')[0]] || 'overview';

function App() {
  // Resolves the current hash into a view name. The reset screen accepts a
  // token query suffix (e.g. #redefinir-senha?token=abc).
  const viewForHash = (raw) => {
    const h = (raw || '').replace('#', '');
    const hashBase = h.split('?')[0];
    // Email links arrive as real paths (/verificar-email?token=…) since CloudFront
    // serves index.html for any path; fall back to the pathname when no hash route.
    const pathBase = (location.pathname || '').replace(/^\/+|\/+$/g, '').split('/')[0];
    const base = hashBase || pathBase;
    if (base === 'onboarding') return 'onboarding';
    if (base === 'login') return 'login';
    if (base === 'esqueci') return 'forgot';
    if (base === 'redefinir-senha') return 'reset';
    if (base === 'verificar-email') return 'verify';
    return 'app';
  };

  // Session gate: an unauthenticated user (no token) only sees the auth/
  // onboarding screens. Authenticated users land on the app.
  const [authed, setAuthed] = useState(() => !!PNH.session.getToken());

  const [view, setView] = useState(viewForHash(location.hash));
  const [role, setRole] = useState('pj');
  const [pjEntity, setPjEntity] = useState({ name: 'Estúdio Norte', initials: 'EN' });
  const [section, setSection] = useState(() => sectionForHash(location.hash));
  const [drawer, setDrawer] = useState(null);
  const [payOpen, setPayOpen] = useState((location.hash || '') === '#novo-pagamento');
  const [payPreset, setPayPreset] = useState(null);
  const [success, setSuccess] = useState(null);
  const [toast, setToast] = useState(null);
  const [me, setMe] = useState(() => PNH.session.getMe());
  const [verifyBusy, setVerifyBusy] = useState(false);

  // Real data loaded from the API once authenticated. Components receive these
  // as props and fall back to [] so wizards never crash before data arrives.
  const [providers, setProviders] = useState([]);
  const [payments, setPayments] = useState([]);
  const [receivables, setReceivables] = useState([]);
  const [contracts, setContracts] = useState([]);
  const [accounts, setAccounts] = useState([]);
  const [loading, setLoading] = useState(false);
  const [hydrating, setHydrating] = useState(() => !!PNH.session.getToken());

  // Pulls the authoritative lists from the API and maps them into the shapes
  // the existing components expect. Each request is independent and tolerant
  // of failure so a single broken endpoint never blanks the whole app.
  const loadData = async () => {
    setLoading(true);
    try {
      const entities = await PNH.api.entities.list().catch(() => []);
      const list = Array.isArray(entities) ? entities : [];
      const byId = {};
      list.forEach(e => { byId[e.id] = e; });
      setProviders(list.map(entityToProvider));

      PNH.api.accounts.mine().then(a => setAccounts(Array.isArray(a) ? a : [])).catch(() => {});

      const [pays, recs, conts] = await Promise.all([
        PNH.api.payments.list().catch(() => []),
        PNH.api.receivables.list().catch(() => []),
        PNH.api.contracts.list().catch(() => []),
      ]);
      setPayments((Array.isArray(pays) ? pays : []).map(p => paymentToFeed(p, byId)));
      setReceivables((Array.isArray(recs) ? recs : []).map(receivableToRow));
      setContracts(Array.isArray(conts) ? conts : []);
    } finally {
      setLoading(false);
    }
  };

  // On first load with a stored token, hydrate role/account from /auth/me and
  // pull the data. A failed /auth/me means the session is stale -> back to login.
  useEffect(() => {
    if (!PNH.session.getToken()) return;
    let cancelled = false;
    (async () => {
      try {
        const me = await PNH.api.auth.me();
        if (cancelled) return;
        PNH.session.setSession({ me });
        applyMe(me);
        setAuthed(true);
        await loadData();
      } catch (e) {
        if (cancelled) return;
        PNH.session.clear();
        setAuthed(false);
      } finally {
        if (!cancelled) setHydrating(false);
      }
    })();
    return () => { cancelled = true; };
  }, []);

  useEffect(() => {
    const onHash = () => {
      const base = (location.hash || '').replace('#', '').split('?')[0];
      if (base === 'novo-pagamento') { setView('app'); setPayOpen(true); return; }
      const v = viewForHash(location.hash);
      setView(v);
      if (v === 'app') setSection(sectionForHash(location.hash));
    };
    window.addEventListener('hashchange', onHash);
    return () => window.removeEventListener('hashchange', onHash);
  }, []);

  // Keep the URL hash in sync with the active section so routes are shareable
  // and survive reload. replaceState avoids polluting history; the modal owns
  // the hash while open.
  useEffect(() => {
    if (view !== 'app') return;
    if (location.hash === '#novo-pagamento') return;
    const want = '#' + (SECTION_TO_HASH[section] || 'painel');
    if (location.hash !== want) history.replaceState(null, '', location.pathname + want);
  }, [section, view]);

  useEffect(() => {
    if (toast) { const t = setTimeout(() => setToast(null), 3200); return () => clearTimeout(t); }
  }, [toast]);

  const openNew = (preset) => { setPayPreset(preset || null); setPayOpen(true); };
  const openContract = () => setSection('new-contract');
  const closeDrawer = () => setDrawer(null);

  const finishPay = (result) => {
    setPayOpen(false);
    setPayPreset(null);
    if (location.hash === '#novo-pagamento') history.replaceState(null, '', location.pathname);
    setSuccess(result);
  };

  // ---------- AUTH ----------
  // Applies an authenticated MeResponse: sets role from account_type and the
  // active account name. Used both on fresh login and on reload hydration.
  const applyMe = (me) => {
    setMe(me);
    const r = me && me.account_type === 'pf' ? 'pf' : 'pj';
    setRole(r);
    if (me && me.account_name) setPjEntity({ name: me.account_name, initials: PNH.initials(me.account_name).toUpperCase() });
  };

  // Resends the email-verification link and reports the result via toast.
  const resendVerification = async () => {
    if (verifyBusy) return;
    setVerifyBusy(true);
    try {
      await PNH.api.auth.requestVerification();
      setToast('Enviamos um novo link de confirmação para o seu e-mail.');
    } catch (err) {
      setToast(err.detail || err.message || 'Não foi possível reenviar agora. Tente em instantes.');
    }
    setVerifyBusy(false);
  };

  // Re-fetches /auth/me so the banner clears as soon as the email is confirmed.
  const refreshVerification = async () => {
    if (verifyBusy) return;
    setVerifyBusy(true);
    try {
      const fresh = await PNH.api.auth.me();
      PNH.session.setSession({ me: fresh });
      applyMe(fresh);
      if (!fresh.email_verified) setToast('Ainda não confirmamos seu e-mail. Verifique sua caixa de entrada.');
    } catch (err) {
      setToast(err.detail || err.message || 'Não foi possível atualizar o status agora.');
    }
    setVerifyBusy(false);
  };

  // Enters the app after a successful login/signup: hydrate identity, mark
  // authenticated, route to the dashboard and load the real data.
  const enterApp = (me, message) => {
    applyMe(me);
    setAuthed(true);
    setSection('overview');
    setView('app');
    history.replaceState(null, '', location.pathname + '#painel');
    if (message) setToast(message);
    loadData();
  };

  // Logs out: best-effort server call, clear the local session, reset to login.
  const logout = async () => {
    try { await PNH.api.auth.logout(); } catch (e) { /* ignore network/expired token */ }
    PNH.session.clear();
    setAuthed(false);
    setProviders([]); setPayments([]); setReceivables([]); setContracts([]);
    setView('login');
    history.replaceState(null, '', location.pathname + '#login');
  };

  // While re-checking a stored token on reload, avoid flashing the login screen.
  if (hydrating) {
    return <div className="ob"><div className="ob-body"><div className="sec" style={{ fontSize: 14 }}>Carregando…</div></div></div>;
  }

  if (view === 'login') {
    return <Login onAuthed={(me) => enterApp(me, 'Bem-vindo de volta!')} />;
  }
  if (view === 'forgot') {
    return <ForgotPassword />;
  }
  if (view === 'reset') {
    return <ResetPassword />;
  }
  if (view === 'verify') {
    return <VerifyEmail />;
  }

  if (view === 'onboarding') {
    return (
      <Onboarding
        goLanding={() => { location.href = 'https://pagonahora.com/'; }}
        onDone={(me) => enterApp(me, 'Conta criada! Bem-vindo à Pago na Hora.')}
      />
    );
  }

  // Session gate: any other route requires authentication. Without a token,
  // fall back to the login screen.
  if (!authed) {
    return <Login onAuthed={(me) => enterApp(me, 'Bem-vindo de volta!')} />;
  }

  // ---------- ACTIONS ----------
  // Creates a contract from the NewContract wizard payload, then refreshes the
  // contracts list. The wizard hands back {party, source, amount, scope, due,
  // payMode, readiness, clauses}.
  const createContract = async (form) => {
    if (!form || !form.party) { setToast('Selecione uma entidade para o contrato.'); return; }
    try {
      const clauses = Object.keys(form.clauses || {}).map(k => ({ clause_key: k, enabled: !!form.clauses[k] }));
      await PNH.api.contracts.create({
        entity_id: form.party.id,
        source: form.source,
        amount_cents: PNH.toCents(form.amount),
        scope: form.scope,
        due_date: form.due,
        pay_mode: form.payMode,
        readiness: form.readiness,
        clauses,
      });
      setSection('contracts');
      setToast('Contrato gerado e pronto para assinatura.');
      try { const c = await PNH.api.contracts.list(); setContracts(Array.isArray(c) ? c : []); } catch (e) { /* keep prior list */ }
    } catch (err) {
      setToast(err.detail || err.message || 'Não foi possível gerar o contrato.');
    }
  };

  // Creates an entity from the NewEntity modal, then refreshes the list. Throws
  // on API error so the modal can surface it; closes + toasts on success.
  const createEntity = async (payload) => {
    await PNH.api.entities.create(payload);
    setSection('providers');
    setToast('Entidade cadastrada.');
    try {
      const ents = await PNH.api.entities.list();
      setProviders((Array.isArray(ents) ? ents : []).map(entityToProvider));
    } catch (e) { /* keep prior list */ }
  };

  // Switches the active owned account (X-Account-Id), re-hydrates identity and
  // reloads that account's data. No-op when it is already active.
  const switchAccount = async (accountId) => {
    if (!accountId || (me && accountId === me.account_id)) return;
    PNH.session.setSession({ account_id: accountId });
    setHydrating(true);
    try {
      const fresh = await PNH.api.auth.me();
      PNH.session.setSession({ me: fresh });
      applyMe(fresh);
      setSection('overview');
      await loadData();
      setToast('Entidade ativa: ' + (fresh.account_name || ''));
    } catch (e) {
      setToast('Não foi possível trocar de entidade.');
    } finally {
      setHydrating(false);
    }
  };

  // Creates a new OWNED account (the dropdown's "cadastrar nova entidade") and
  // switches into it. Throws on API error so the form can surface it.
  const createOwnedAccount = async (payload) => {
    const acct = await PNH.api.accounts.create(payload);
    await switchAccount(acct.id);
  };

  // Creates payments from the NewPayment wizard. items already carry
  // amount_cents/entity_id; when sending "agora", approve each created payment.
  const createPayments = async (data) => {
    try {
      const created = await PNH.api.payments.create(data.items);
      const list = Array.isArray(created) ? created : (created ? [created] : []);
      if (data.when === 'agora') {
        await Promise.all(list.map(p => PNH.api.payments.approve(p.id).catch(() => null)));
      }
      finishPay({ count: data.count, total: data.total, when: data.when });
      try {
        const pays = await PNH.api.payments.list();
        const byId = {}; providers.forEach(pv => { byId[pv.id] = { name: pv.name, kind: pv.kind, pix_type: pv.pixType }; });
        setPayments((Array.isArray(pays) ? pays : []).map(p => paymentToFeed(p, byId)));
      } catch (e) { /* keep prior list */ }
    } catch (err) {
      setToast(err.detail || err.message || 'Não foi possível concluir o pagamento.');
    }
  };

  // ---------- APP ----------
  const titles = {
    pj: {
      overview: { t: 'Visão geral', s: pjEntity.name + ' · PJ · Junho 2026', search: 'Buscar entidade, contrato ou pagamento…' },
      assistant: { t: 'Assistente', s: 'Sua IA da Pago na Hora', noSearch: true },
      payments: { t: 'Pagar', s: 'Pagamentos enviados, agendados e pendentes', search: 'Buscar pagamento…' },
      receivables: { t: 'Receber', s: 'Entradas previstas, aprovadas e recebidas', search: 'Buscar entrada…' },
      providers: { t: 'Entidades', s: 'Suas entidades', search: 'Buscar entidade…' },
      'new-entity': { t: 'Nova entidade', s: 'Cadastre uma entidade para pagar, receber ou contratar', noSearch: true },
      'new-account': { t: 'Nova entidade própria', s: 'Crie outra entidade que você administra (sua PF, sua PJ)', noSearch: true },
      contracts: { t: 'Contratos', s: 'Modelos e assinaturas', search: 'Buscar contrato…' },
      'new-contract': { t: 'Novo contrato', s: pjEntity.name + ' · PJ', noSearch: true },
      documents: { t: 'Documentos', s: 'Contratos, notas e comprovantes', search: 'Buscar documento…' },
    },
    pf: {
      overview: { t: 'Visão geral', s: pjEntity.name + ' · PF · Junho 2026', search: 'Buscar entidade…' },
      assistant: { t: 'Assistente', s: 'Sua IA da Pago na Hora', noSearch: true },
      payments: { t: 'Pagar', s: 'Pagamentos enviados, agendados e pendentes', search: 'Buscar pagamento…' },
      receivables: { t: 'Receber', s: 'Entradas previstas, aprovadas e recebidas', search: 'Buscar entrada…' },
      providers: { t: 'Entidades', s: 'Suas entidades', search: 'Buscar entidade…' },
      'new-entity': { t: 'Nova entidade', s: 'Cadastre uma entidade para pagar, receber ou contratar', noSearch: true },
      'new-account': { t: 'Nova entidade própria', s: 'Crie outra entidade que você administra (sua PF, sua PJ)', noSearch: true },
      contracts: { t: 'Contratos', s: 'Seus contratos e assinaturas', search: 'Buscar contrato…' },
      'new-contract': { t: 'Novo contrato', s: pjEntity.name + ' · PF', noSearch: true },
      documents: { t: 'Documentos', s: 'Contratos, notas e comprovantes', search: 'Buscar documento…' },
    },
  };
  const meta = titles[role][section] || titles[role].overview;

  let body;
  if (section === 'assistant') body = <Assistant key={role} role={role} providers={providers} payments={payments} receivables={receivables} />;
  else if (section === 'new-entity') body = <NewEntity onClose={() => setSection('providers')} onDone={createEntity} />;
  else if (section === 'new-account') body = <NewAccount onClose={() => setSection('overview')} onDone={createOwnedAccount} />;
  else if (role === 'pj') {
    if (section === 'overview') body = <OverviewPJ payments={payments} providers={providers} loading={loading} onNew={() => openNew()} openProvider={setDrawer} />;
    else if (section === 'payments') body = <PaymentsPJ payments={payments} providers={providers} loading={loading} onNew={() => openNew()} openProvider={setDrawer} />;
    else if (section === 'receivables') body = <ReceivablesPF receivables={receivables} loading={loading} />;
    else if (section === 'providers') body = <ProvidersPJ providers={providers} loading={loading} openProvider={setDrawer} onNew={() => setSection('new-entity')} />;
    else if (section === 'contracts') body = <ContractsView role="pj" contracts={contracts} providers={providers} onNew={openContract} />;
    else if (section === 'new-contract') body = <NewContract role="pj" entity={pjEntity.name} providers={providers} onClose={() => setSection('contracts')} onDone={createContract} />;
    else body = <DocumentsView role="pj" />;
  } else {
    if (section === 'overview') body = <OverviewPF receivables={receivables} loading={loading} />;
    else if (section === 'payments') body = <PaymentsPJ payments={payments} providers={providers} loading={loading} onNew={() => openNew()} openProvider={setDrawer} />;
    else if (section === 'receivables') body = <ReceivablesPF receivables={receivables} loading={loading} />;
    else if (section === 'providers') body = <ProvidersPJ providers={providers} loading={loading} openProvider={setDrawer} onNew={() => setSection('new-entity')} />;
    else if (section === 'contracts') body = <ContractsView role="pf" contracts={contracts} providers={providers} onNew={openContract} />;
    else if (section === 'new-contract') body = <NewContract role="pf" entity={pjEntity.name} providers={providers} onClose={() => setSection('contracts')} onDone={createContract} />;
    else body = <DocumentsView role="pf" />;
  }

  return (
    <div className="app">
      <Sidebar role={role} setRole={setRole} section={section} setSection={setSection} pjEntity={pjEntity} setPjEntity={setPjEntity} onLogout={logout} me={me} accounts={accounts} onSwitch={switchAccount} onNewOwned={() => setSection('new-account')} />
      <div className="main">
        <Topbar title={meta.t} sub={meta.s} search={meta.search} noSearch={meta.noSearch} />
        {me && me.email_verified === false && (
          <div className="verify-banner">
            <span className="vb-ic"><Icon name="bell" size={17} color="var(--amber)" /></span>
            <span className="vb-txt">
              Confirme seu e-mail{me.email ? <> (<b>{me.email}</b>)</> : ''} para proteger a conta e liberar pagamentos.
            </span>
            <button className="vb-btn" onClick={resendVerification} disabled={verifyBusy}>Reenviar link</button>
            <button className="vb-link" onClick={refreshVerification} disabled={verifyBusy}>Já confirmei</button>
          </div>
        )}
        {body}
      </div>

      {drawer && <ProviderDrawer provider={drawer} payments={payments} onClose={closeDrawer} onPay={(pv) => { closeDrawer(); openNew(pv); }} />}
      {payOpen && <NewPayment preset={payPreset} providers={providers} onClose={() => { setPayOpen(false); if (location.hash === '#novo-pagamento') history.replaceState(null, '', location.pathname); }} onDone={createPayments} />}
      {success && <PaySuccess result={success} onClose={() => { setSuccess(null); setSection('payments'); }} />}

      {toast && (
        <div className="toast">
          <span className="ic"><Icon name="check" size={16} color="var(--green)" sw={3} /></span>
          {toast}
        </div>
      )}
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
