// Modal pages — AI statement, How we use data, Privacy/GDPR, "We don't store your data".

// Redacted excerpt of functions/api/analyze.ts. Prompts are intentionally
// removed (those are the IP); the data-flow code is verbatim. SHA below
// hashes the full unredacted file as deployed.
const ANALYZE_SHA256 = '760d1fea177e19f230cff65f440e0e8a8c667cb605c7a59c00058013e079f58a';
const ANALYZE_EXCERPT = `// functions/api/analyze.ts — data-flow excerpt (prompts redacted)

export const onRequestPost: PagesFunction<Env> = async (context) => {
  const { request, env } = context;

  const form = await request.formData();
  const file = form.get('file');                   // in-memory only
  if (!(file instanceof File)) { /* ... */ }

  const buf = await file.arrayBuffer();            // transient buffer
  const b64 = arrayBufferToBase64(buf);            // transient string

  // The ONLY outbound request the function makes:
  const upstream = await fetch(
    'https://api.anthropic.com/v1/messages',
    {
      method: 'POST',
      headers: {
        'content-type': 'application/json',
        'x-api-key': env.ANTHROPIC_API_KEY,
        'anthropic-version': '2023-06-01',
      },
      body: JSON.stringify({
        model: MODEL,
        max_tokens: 4096,
        system: /* prompt redacted */,
        messages: [{
          role: 'user',
          content: [mediaBlock, { type: 'text', text: /* prompt redacted */ }],
        }],
      }),
    },
  );

  // Response is parsed and returned. The function returns and exits.
  // V8 garbage-collects buf, b64, file, and the request body.
  // No KV.put, no R2.put, no D1.prepare, no console.log of file content,
  // no fetch to any logging endpoint anywhere in this file.
};`;

const SourceFlyout = ({ open, onClose }) => {
  React.useEffect(() => {
    if (!open) return;
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [open, onClose]);

  if (!open) return null;

  return (
    <div className="source-flyout-scrim" onClick={onClose}>
      <aside className="source-flyout" onClick={(e) => e.stopPropagation()}>
        <div className="source-flyout-head">
          <span className="eyebrow">/ Data-flow excerpt</span>
          <button className="modal-close" onClick={onClose} aria-label="Close">×</button>
        </div>
        <p className="source-intro">
          Below is the data-handling portion of <code>functions/api/analyze.ts</code>.
          Prompts are redacted (that's our IP) but the data flow — where the file goes,
          what we log, what we store — is verbatim from the deployed source.
        </p>
        <pre className="source-block"><code>{ANALYZE_EXCERPT}</code></pre>
        <div className="source-foot">
          <span>SHA-256 of the full deployed file:</span>
          <code className="source-sha">{ANALYZE_SHA256}</code>
        </div>
      </aside>
    </div>
  );
};

const StorageBody = () => {
  const [showSource, setShowSource] = React.useState(false);
  return (
    <React.Fragment>
      <div className="promise">
        <b>Zero-retention by default.</b>
        The PDF or image you upload is held in volatile memory only.
        When the analysis finishes — or after 5 minutes, whichever is first — it is gone.
        <span className="zdr-tag" title="Anthropic Zero Data Retention request filed">ZDR pending</span>
      </div>

      <h2>What that means in practice</h2>
      <ul>
        <li>No copy is written to disk on our servers.</li>
        <li>No copy is written to disk on any model provider we use.</li>
        <li>You will not find your bill in a "history" tab — there isn't one.</li>
        <li>If you want a record, download the PDF report at the end of the analysis. That copy lives on your device, not ours.</li>
      </ul>

      <h2>What we do keep</h2>
      <ul>
        <li>The fact that <i>some</i> analysis ran, with timestamps, for abuse-prevention only.</li>
      </ul>

      <h2>Why this is the default</h2>
      <p>
        Bills are sensitive in ways most product teams understand only after a breach.
        We'd rather not be the team that learns the lesson the hard way — and we'd rather you
        not be the user who teaches them. The cheapest way to keep something safe is to not
        keep it at all.
      </p>

      <h2>Verify it yourself</h2>
      <p>
        The function that handles your file is closed source — but the part that handles
        the data-flow is published here in full. There is no database write, no logging of
        file content, and exactly one outbound request: to Anthropic's API.
      </p>
      <p>
        <button className="btn ghost" onClick={() => setShowSource(true)}>
          Show data-flow excerpt →
        </button>
      </p>

      <SourceFlyout open={showSource} onClose={() => setShowSource(false)} />
    </React.Fragment>
  );
};

// Obfuscate the email so static-HTML scrapers don't grab it.
const obfuscatedMailto = (e) => {
  if (e) e.preventDefault();
  window.location.href = 'mailto:' + ['contact', 'checkthisbill.com'].join('@');
};

const ContactBody = () => {
  const [email, setEmail] = React.useState('');
  const [message, setMessage] = React.useState('');
  const [turnstileToken, setTurnstileToken] = React.useState(null);
  const [submitting, setSubmitting] = React.useState(false);
  const [done, setDone] = React.useState(false);
  const [error, setError] = React.useState(null);
  const tsMountRef = React.useRef(null);
  const tsWidgetIdRef = React.useRef(null);

  React.useEffect(() => {
    let cancelled = false;
    const tryMount = () => {
      if (cancelled) return;
      if (!window.turnstile || !tsMountRef.current) {
        setTimeout(tryMount, 200);
        return;
      }
      const sitekey = window.TURNSTILE_SITE_KEY;
      if (!sitekey || sitekey === '__TURNSTILE_SITE_KEY__') {
        setError('Verification is not configured. Please use the email link below.');
        return;
      }
      try {
        tsWidgetIdRef.current = window.turnstile.render(tsMountRef.current, {
          sitekey,
          callback: (token) => setTurnstileToken(token),
          'expired-callback': () => setTurnstileToken(null),
          'error-callback': () => setTurnstileToken(null),
        });
      } catch (e) {
        // already rendered or other transient — ignore
      }
    };
    tryMount();
    return () => {
      cancelled = true;
      if (tsWidgetIdRef.current && window.turnstile?.remove) {
        try { window.turnstile.remove(tsWidgetIdRef.current); } catch {}
      }
    };
  }, []);

  const submit = async (e) => {
    e.preventDefault();
    setError(null);
    if (!message.trim()) {
      setError('Please enter a message.');
      return;
    }
    if (!turnstileToken) {
      setError('Please complete the verification check above.');
      return;
    }
    setSubmitting(true);
    try {
      const r = await fetch('/api/contact', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ email, message, turnstileToken }),
      });
      const data = await r.json().catch(() => ({}));
      if (!r.ok) throw new Error(data?.error || 'Could not send. Please try again.');
      setDone(true);
    } catch (err) {
      setError(String(err?.message || err));
      if (tsWidgetIdRef.current && window.turnstile?.reset) {
        try { window.turnstile.reset(tsWidgetIdRef.current); } catch {}
      }
      setTurnstileToken(null);
    } finally {
      setSubmitting(false);
    }
  };

  if (done) {
    return (
      <React.Fragment>
        <div className="promise">
          <b>Thanks — message received.</b>
          We read everything that comes in. If you left an email, we'll reply when we can.
        </div>
      </React.Fragment>
    );
  }

  return (
    <React.Fragment>
      <p>Question, bug, suggestion, or just hello — drop a note. No account needed.</p>

      <form className="contact-form" onSubmit={submit} noValidate>
        <label className="cf-label">
          <span>Your email <i>(optional, so we can reply)</i></span>
          <input
            type="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            placeholder="you@example.com"
            autoComplete="email"
            spellCheck="false"
          />
        </label>
        <label className="cf-label">
          <span>Message</span>
          <textarea
            value={message}
            onChange={(e) => setMessage(e.target.value)}
            rows={6}
            maxLength={4000}
            required
          />
        </label>

        <div ref={tsMountRef} className="cf-turnstile-mount"></div>

        {error && <div className="cf-error">{error}</div>}

        <div className="cf-actions">
          <button
            type="submit"
            className="btn accent"
            disabled={submitting || !turnstileToken || !message.trim()}
          >
            {submitting ? 'Sending…' : 'Send message →'}
          </button>
        </div>
      </form>

      <p className="cf-fallback">
        Prefer your own email client?{' '}
        <a href="#" onClick={obfuscatedMailto} style={{ borderBottom: '1px solid currentColor' }}>
          Email us directly
        </a>.
      </p>
    </React.Fragment>
  );
};

const PAGES = {
  ai: {
    title: "AI statement",
    deck: "How AI participates in your bill analysis, where it doesn't, and what we do to keep it honest.",
    body: () => (
      <React.Fragment>
        <div className="promise">
          <b>The short version.</b>
          We use a large language model to read your bill, extract line items, and explain what they mean.
          We do not let the model decide what is owed, what is fraud, or what your legal options are.
          Those framings are written by humans and reviewed quarterly.
        </div>

        <h2>Where the model helps</h2>
        <ul>
          <li>Parsing free-form text and tables out of PDFs and photos.</li>
          <li>Mapping vague line descriptions onto plain-English ones — across medical, internet, utility, hotel, repair, and subscription bills.</li>
          <li>Explaining what each charge actually is, in language a non-specialist can verify.</li>
          <li>Producing the first draft of a negotiation script that you can edit before using.</li>
        </ul>

        <h2>Where the model does not decide</h2>
        <ul>
          <li>The model can be wrong. Treat flagged lines as a starting point for your own questions, not a verdict.</li>
          <li>"Likely recoverable" amounts are estimates the model produces from common practice. They are not promises and they are not legal advice.</li>
          <li>Whether to dispute, pay, or escalate is your call. We give you the script; you make the decision.</li>
        </ul>

        <h2>Models we use</h2>
        <p>
          Document parsing and explanation run on Anthropic Claude.
          Your bill is sent as a one-time prompt and is not used to train that model.
          We have requested zero data retention from Anthropic; until that is granted, their standard
          retention applies.
        </p>

        <h2>Mistakes</h2>
        <p>
          AI gets things wrong. So do humans. So do hospitals. Treat our output as a sharp second
          opinion — not a final answer. We surface uncertainty by marking lines as <i>flagged</i>,
          <i> negotiable</i>, or <i>within range</i>, and we tell you when the underlying market
          data is thin.
        </p>
      </React.Fragment>
    )
  },

  data: {
    title: "How we use your data",
    deck: "Plain-English answer to the question: what happens to my bill after I upload it?",
    body: () => (
      <React.Fragment>
        <div className="promise">
          <b>One sentence.</b>
          Your bill is parsed in memory, returned to you as an analysis, and discarded —
          unless you opt in to anonymized benchmarking, in which case the line-item amounts
          (without your name, account, or address) help everyone else negotiate better.
        </div>

        <h2>What happens by default</h2>
        <ul>
          <li>The file is held in memory for the duration of one analysis and then released.</li>
          <li>Operational metadata (request timing, error rates) without bill content.</li>
          <li>Burst rate-limiting at the network edge to stop scripted abuse.</li>
        </ul>

        <h2>What we never do</h2>
        <ul>
          <li>Sell your data to insurers, employers, advertisers, or data brokers.</li>
          <li>Use your bill content to train any AI model.</li>
          <li>Build a profile of you across visits — we don't have accounts.</li>
        </ul>
      </React.Fragment>
    )
  },

  privacy: {
    title: "Privacy & compliance",
    deck: "What we do, what we don't, and what we depend on. Written to be checkable, not impressive.",
    body: ({ onPage }) => (
      <React.Fragment>
        <div className="promise">
          <b>Plain summary.</b>
          No accounts. No login. No bill content stored. No analytics tracking.
          Three external services see anything at all: Cloudflare (hosting), Anthropic (analysis), Stripe (donations only). Each is described below.
        </div>

        <h2>What we collect</h2>
        <ul>
          <li><b>The bill you upload</b> — held in server memory for one analysis, then released. Not written to disk on our side. Not logged.</li>
          <li><b>Your IP address, transiently</b> — visible to Cloudflare for rate-limiting and routing. We do not store it.</li>
          <li><b>Operational metadata</b> — request timing, status codes, error rates. No bill content, no identifying information.</li>
          <li><b>One browser preference</b> — your dark-mode choice, stored only in your browser's <code>localStorage</code> as <code>ctb-dark</code>. Never sent to us.</li>
        </ul>

        <h2>What we don't collect</h2>
        <ul>
          <li>No accounts, no email addresses, no passwords.</li>
          <li>No tracking cookies, no advertising pixels, no analytics SDKs.</li>
          <li>No fingerprinting, no cross-site identifiers.</li>
          <li>No bill content beyond the duration of one analysis.</li>
        </ul>

        <h2>Third parties (subprocessors)</h2>
        <ul>
          <li><b>Cloudflare</b> — hosts the site and runs the analysis function. Sees your IP and request metadata. We do not enable Cloudflare Logpush, so request bodies are not exported anywhere.</li>
          <li><b>Anthropic</b> — receives the bill once during analysis. Standard policy retains inputs up to 30 days for safety review. We have requested zero data retention <span className="zdr-tag" title="Anthropic Zero Data Retention request filed">ZDR pending</span>; once granted, retention there drops to zero.</li>
          <li><b>Stripe</b> — only involved if you choose to donate. They handle the payment and see whatever you give them at checkout. We do not receive or store payment details.</li>
        </ul>

        <h2>International transfers</h2>
        <p>
          Cloudflare and Anthropic operate in the United States. If you are outside the US,
          your bill is transferred to the US during the analysis under the legitimate-interest basis
          of providing the service you requested. The data is not retained on our side after the analysis.
        </p>

        <h2>Your rights — EU/UK · California · other US states</h2>
        <p>
          Because we run no accounts and retain no bill content, most data-subject requests
          ("export my data", "delete my data") have nothing to act on. The bill is already gone.
          If you believe we hold something about you that you'd like reviewed, removed, or corrected,
          use the <a onClick={() => onPage && onPage('contact')} style={{ borderBottom: '1px solid currentColor', cursor: 'pointer' }}>contact form</a> and
          we will check. We do not sell or share personal information for cross-context
          behavioral advertising and we honor Global Privacy Control signals automatically.
        </p>

        <h2>Children</h2>
        <p>
          Checkthisbill is not directed at people under 16. A parent or guardian uploading a bill
          on behalf of a minor processes that bill on the minor's behalf; the same in-memory-only
          handling applies.
        </p>

        <h2>Verify it yourself</h2>
        <p>
          A redacted excerpt of the function that handles your file is published on the{' '}
          <i>We don't store your bill</i> page (linked from the footer). It shows the data flow
          line by line: there is no database write, no logging of file content, and exactly one
          outbound request from that function — to Anthropic's API.
        </p>

        <h2>Changes to this policy</h2>
        <p>
          We post changes here. Material changes will be flagged at the top of this page for at
          least 30 days. When ZDR is granted by Anthropic, we will update the language above.
        </p>
      </React.Fragment>
    )
  },

  storage: {
    title: "We don't store your bill.",
    deck: "Most billing tools want everything: your inbox, your bank, your card. We want about thirty seconds.",
    body: StorageBody
  },

  how: {
    title: "How it works",
    deck: "Upload, read, explain, negotiate. Four moves. About thirty seconds end-to-end.",
    body: () => (
      <React.Fragment>
        <h2>1. Upload</h2>
        <p>
          Drag a PDF, snap a photo, or paste a screenshot. Internet bills, medical statements,
          repair quotes, subscription invoices, hotel folios. We support most consumer billing formats
          and a growing set of business invoices.
        </p>

        <h2>2. Read</h2>
        <p>
          We extract every line — codes, descriptions, quantities, amounts. We normalize vague
          surcharge names against a vocabulary built from millions of real bills, so
          "Administrative Recovery Fee", "Cost Recovery Surcharge", and "Operational Adjustment"
          all resolve to the same family.
        </p>

        <h2>3. Explain</h2>
        <p>
          Each line gets one of three labels — <i>flagged</i>, <i>negotiable</i>, or <i>within range</i> —
          and a one-sentence explanation in plain English. If a line shouldn't be there, we say so,
          and we tell you why.
        </p>

        <h2>4. Negotiate</h2>
        <p>
          We hand you a script tuned to the actual issues we found. Calm, specific, and short.
          Use it on a phone call, paste it into live chat, or attach it to a written dispute.
          Then close the tab and get on with your life.
        </p>
      </React.Fragment>
    )
  },

  monitor: {
    title: "Bill creep monitoring",
    deck: "Coming soon — we'll watch your future bills against this baseline and ping you only when something changes.",
    body: () => (
      <React.Fragment>
        <p>
          One-time analysis is most of the value. But if you'd like, we can hold a checksum
          of this bill (no content, just structural fingerprints) and compare it against future
          uploads. When a fee creeps, a new line appears, or a promo expires, we'll send one email.
          No dashboards, no daily reminders.
        </p>
        <p style={{ color: 'var(--ink-3)' }}>
          Premium feature, $4/month, opt-in. Available in beta from June.
        </p>
      </React.Fragment>
    )
  },

  contact: {
    title: "Contact",
    deck: "",
    body: ContactBody
  }
};

const ModalPage = ({ pageKey, onClose, onPage }) => {
  const isOpen = !!(pageKey && PAGES[pageKey]);

  React.useEffect(() => {
    if (!isOpen) return;
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    document.body.style.overflow = 'hidden';
    return () => {
      window.removeEventListener('keydown', onKey);
      document.body.style.overflow = '';
    };
  }, [isOpen, onClose]);

  if (!isOpen) return null;
  const page = PAGES[pageKey];

  return (
    <div className="modal-page" onClick={onClose}>
      <div className="modal-inner" onClick={e => e.stopPropagation()}>
        <button className="modal-close" onClick={onClose}>×</button>
        <div className="legal">
          <span className="eyebrow">/ {page.title}</span>
          <h1 style={{ marginTop: 16 }}>{page.title}</h1>
          {page.deck && <p className="deck">{page.deck}</p>}
          {React.createElement(page.body, { onPage })}
          <div style={{
            marginTop: 48, paddingTop: 24,
            borderTop: '1px solid var(--line-2)',
            fontSize: 'var(--fz-micro)', letterSpacing: '0.06em',
            color: 'var(--ink-3)', display: 'flex', justifyContent: 'space-between',
            gap: 16, flexWrap: 'wrap'
          }}>
            <span>Last updated · May 2026</span>
            {pageKey !== 'contact' && (
              <a onClick={() => onPage && onPage('contact')}
                 style={{ color: 'var(--ink-3)', borderBottom: '1px solid var(--line)', cursor: 'pointer' }}>
                Contact
              </a>
            )}
          </div>
        </div>
      </div>
    </div>
  );
};

Object.assign(window, { ModalPage });
