// Fossyl primitives — shared building blocks

const { useState, useEffect, useMemo, useRef, createContext, useContext } = React;

// ---------- Logo / wordmark ----------
function Logo({ size = 22, withText = true, ink }) {
  const color = ink || 'var(--ink)';
  return (
    <div className="wordmark" style={{ color, fontSize: size * 0.88 }}>
      <span className="glyph" style={{ width: size, height: size, fontSize: size * 0.6, borderColor: color }}>F</span>
      {withText && <span style={{ fontSize: size * 0.82 }}>Fossyl</span>}
    </div>
  );
}

// ---------- Fossil iconographic placeholder ----------
function FossilPlaceholder({ w = 260, h = 180, label = "STRATIGRAPHIC MAP", sub = "placeholder" }) {
  return (
    <div style={{
      width: w, height: h, position: 'relative',
      background: 'var(--bg-1)',
      border: '1px solid var(--line)',
      borderRadius: 3,
      overflow: 'hidden',
    }}>
      <svg width="100%" height="100%" style={{ position: 'absolute', inset: 0, opacity: 0.5 }}>
        <defs>
          <pattern id={`stripe-${w}-${h}`} width="6" height="6" patternUnits="userSpaceOnUse" patternTransform="rotate(30)">
            <line x1="0" y1="0" x2="0" y2="6" stroke="var(--line-2)" strokeWidth="1"/>
          </pattern>
        </defs>
        <rect width="100%" height="100%" fill={`url(#stripe-${w}-${h})`} />
      </svg>
      <div style={{
        position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column',
        alignItems: 'center', justifyContent: 'center', gap: 4,
      }}>
        <div className="caps" style={{ color: 'var(--muted)' }}>{label}</div>
        <div className="mono" style={{ fontSize: 10.5, color: 'var(--dim)' }}>{sub}</div>
      </div>
    </div>
  );
}

// ---------- Measurement rule ----------
function Rule({ label, value, align = 'left' }) {
  return (
    <div className="col" style={{ gap: 2 }}>
      {label && <span className="caps">{label}</span>}
      <div className="row ai-e g-8">
        <div className="num" style={{ fontFamily: 'var(--serif)', fontSize: 32, fontWeight: 400, lineHeight: 1, letterSpacing: '-0.02em' }}>{value}</div>
      </div>
    </div>
  );
}

// ---------- Corner-marked frame ----------
function Framed({ children, style, ...rest }) {
  return (
    <div className="corners" style={{ border: '1px solid var(--line)', ...style }} {...rest}>
      <span className="cbl"/>
      <span className="cbr"/>
      {children}
    </div>
  );
}

// ---------- Section header with eyebrow ----------
function SectionHeader({ number, eyebrow, title, kicker, right }) {
  return (
    <div className="row jc-b ai-s" style={{ padding: '20px 0', borderBottom: '1px solid var(--line)', gap: 24 }}>
      <div className="col g-8" style={{ flex: 1 }}>
        <div className="row g-16 ai-c">
          {number && <span className="mono" style={{ color: 'var(--dim)', fontSize: 11, letterSpacing: '0.1em' }}>§ {number}</span>}
          {eyebrow && <span className="caps">{eyebrow}</span>}
        </div>
        <h2 className="display" style={{ margin: 0, fontSize: 34, fontWeight: 400 }}>{title}</h2>
        {kicker && <p className="ink-dim" style={{ margin: 0, fontSize: 14, maxWidth: 640 }}>{kicker}</p>}
      </div>
      {right && <div>{right}</div>}
    </div>
  );
}

// ---------- Stat block ----------
function Stat({ label, value, unit, tone }) {
  const toneClass = tone ? tone : '';
  return (
    <div className="col g-8" style={{ padding: '16px 20px', borderLeft: '1px solid var(--line)', flex: 1, minWidth: 140 }}>
      <span className="caps">{label}</span>
      <div className="row ai-e g-4">
        <span className="num" style={{ fontFamily: 'var(--serif)', fontSize: 38, lineHeight: 1, fontWeight: 400, letterSpacing: '-0.02em' }} className={toneClass ? toneClass : undefined}>{value}</span>
        {unit && <span className="mono" style={{ fontSize: 11, color: 'var(--muted)', paddingBottom: 5, marginLeft: 6 }}>{unit}</span>}
      </div>
    </div>
  );
}

// ---------- Risk bar ----------
function RiskBar({ value, max = 100 }) {
  const pct = Math.min(100, (value / max) * 100);
  const tone = value > 70 ? 'var(--rust)' : value > 40 ? 'var(--bone)' : 'var(--lichen)';
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
      <div style={{ width: 60, height: 4, background: 'var(--bg-2)', borderRadius: 1, overflow: 'hidden' }}>
        <div style={{ width: pct + '%', height: '100%', background: tone }}/>
      </div>
      <span className="num" style={{ fontSize: 11, color: 'var(--muted)', minWidth: 26 }}>{value}</span>
    </div>
  );
}

// ---------- Ticks (measurement) ----------
function Ticks({ count = 40, height = 10 }) {
  return (
    <svg width="100%" height={height} style={{ display: 'block' }}>
      {[...Array(count)].map((_, i) => (
        <line
          key={i}
          x1={`${(i / (count - 1)) * 100}%`}
          x2={`${(i / (count - 1)) * 100}%`}
          y1={0}
          y2={i % 5 === 0 ? height : height * 0.5}
          stroke="var(--line-2)"
          strokeWidth="1"
        />
      ))}
    </svg>
  );
}

// ---------- Field-note margin mark ----------
function FieldNote({ index, children }) {
  return (
    <div className="row g-12 ai-s" style={{ padding: '8px 0' }}>
      {index != null && <span className="mono" style={{ color: 'var(--dim)', fontSize: 10.5, paddingTop: 2, minWidth: 32 }}>F-{String(index).padStart(4, '0')}</span>}
      <div className="marginalia" style={{ flex: 1 }}>{children}</div>
    </div>
  );
}

// ---------- Nav context (sidebar/top-nav/doc) ----------
const NavContext = createContext({ current: 'landing', go: () => {} });
function useNav() { return useContext(NavContext); }

// ---------- Auth context ----------
const AuthContext = createContext({
  authed: false, paid: false, hasRun: false,
  signIn: () => {}, signUp: () => {}, signOut: () => {},
  setPaid: () => {}, setHasRun: () => {}, plan: null, setPlan: () => {},
});
function useAuth() { return useContext(AuthContext); }

// ---------- Toast ----------
const ToastContext = createContext({ toast: () => {} });
function useToast() { return useContext(ToastContext); }

function ToastProvider({ children }) {
  const [items, setItems] = useState([]);
  const toast = (msg, opts = {}) => {
    const id = Math.random().toString(36).slice(2);
    window.__fossylToast = toast; // register globally for out-of-component callers
    const tone = opts.tone || 'default';
    setItems((xs) => [...xs, { id, msg, tone }]);
    setTimeout(() => setItems((xs) => xs.filter((x) => x.id !== id)), opts.duration || 2500);
  };
  return (
    <ToastContext.Provider value={{ toast }}>
      {children}
      <div style={{
        position: 'fixed', bottom: 24, left: '50%', transform: 'translateX(-50%)',
        zIndex: 200, display: 'flex', flexDirection: 'column', gap: 8, alignItems: 'center',
        pointerEvents: 'none',
      }}>
        {items.map((t) => (
          <div key={t.id} style={{
            background: 'var(--panel)',
            border: '1px solid ' + (t.tone === 'error' ? 'var(--rust-dim)' : t.tone === 'success' ? 'var(--lichen-dim)' : 'var(--line-2)'),
            color: t.tone === 'error' ? 'var(--rust)' : t.tone === 'success' ? 'var(--lichen)' : 'var(--ink)',
            fontFamily: 'var(--mono)', fontSize: 12,
            padding: '10px 16px',
            boxShadow: '0 8px 30px rgba(0,0,0,0.4)',
            borderRadius: 2,
            animation: 'toastIn 200ms ease-out',
          }}>
            <span style={{ color: 'var(--dim)', marginRight: 10 }}>◈</span>{t.msg}
          </div>
        ))}
      </div>
    </ToastContext.Provider>
  );
}

// ---------- Workspace join modal (shown to invited users on login) ----------
function WorkspaceJoinModal({ invite, onAccept, onDecline, accepting }) {
  const { workspace_name, invited_by, role } = invite;
  const roleDesc = {
    Viewer: 'can browse existing excavation reports but cannot start new scans.',
    Member: 'can run new excavations and access all reports.',
    Admin:  'can run excavations, view reports, and manage workspace members.',
  }[role] || '';

  return (
    <div style={{
      position: 'fixed', inset: 0, zIndex: 400,
      background: 'rgba(0,0,0,0.75)', display: 'flex',
      alignItems: 'center', justifyContent: 'center', padding: 24,
    }}>
      <div style={{ background: 'var(--bg)', border: '1px solid var(--line)', width: '100%', maxWidth: 500 }}>
        <div style={{ padding: '18px 24px', borderBottom: '1px solid var(--line)' }}>
          <span className="caps">Workspace invitation</span>
        </div>
        <div style={{ padding: '28px 28px 20px' }}>
          <div className="display" style={{ fontSize: 26, fontWeight: 400, letterSpacing: '-0.02em', lineHeight: 1.1, marginBottom: 16 }}>
            You've been invited to join <span style={{ color: 'var(--bone)' }}>{workspace_name}</span>.
          </div>
          <p style={{ margin: '0 0 16px', fontSize: 13, color: 'var(--ink-dim)', lineHeight: 1.6 }}>
            <span style={{ color: 'var(--ink)' }}>{invited_by}</span> added you as a{' '}
            <span style={{ color: 'var(--bone)' }}>{role}</span>.{roleDesc ? ` ${role}s ${roleDesc}` : ''}
          </p>
          <div style={{ padding: '12px 16px', background: 'var(--bg-1)', border: '1px solid var(--line)', fontSize: 12, color: 'var(--ink-dim)', lineHeight: 1.5 }}>
            Accepting moves you into <span style={{ color: 'var(--ink)' }}>{workspace_name}</span>'s workspace and grants you access to their active plan.
          </div>
        </div>
        <div style={{ padding: '14px 24px', borderTop: '1px solid var(--line)', display: 'flex', justifyContent: 'flex-end', gap: 10, background: 'var(--bg-1)' }}>
          <button className="btn" onClick={onDecline} disabled={accepting}>Decline</button>
          <button className="btn btn-primary" onClick={onAccept} disabled={accepting}>
            {accepting ? 'Joining…' : 'Accept invitation →'}
          </button>
        </div>
      </div>
    </div>
  );
}

// ---------- Terms modal ----------
function TermsModal({ onAccept, onDecline }) {
  return (
    <div style={{
      position: 'fixed', inset: 0, zIndex: 400,
      background: 'rgba(0,0,0,0.75)', display: 'flex',
      alignItems: 'center', justifyContent: 'center', padding: 24,
    }}
      onClick={e => { if (e.target === e.currentTarget) onDecline(); }}
    >
      <div style={{
        background: 'var(--bg)', border: '1px solid var(--line)',
        width: '100%', maxWidth: 660, maxHeight: '82vh',
        display: 'flex', flexDirection: 'column',
      }}>
        <div style={{ padding: '18px 24px', borderBottom: '1px solid var(--line)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
          <span className="caps">Fossyl — Terms of service &amp; data handling</span>
          <span className="mono" style={{ fontSize: 10.5, color: 'var(--muted)' }}>rev. 2026-05</span>
        </div>

        <div style={{ overflow: 'auto', padding: '24px 28px', flex: 1 }}>
          <div style={{ fontSize: 13, lineHeight: 1.7, color: 'var(--ink-dim)', display: 'flex', flexDirection: 'column', gap: 24 }}>

            <div>
              <div className="caps" style={{ marginBottom: 10, color: 'var(--ink)' }}>§ 1 · Service</div>
              <p style={{ margin: 0 }}>Fossyl ("the Service") analyses Plone and Zope codebases from files you upload (repository ZIP, ZODB export, optional SQL schema). The Service is operated by Fossyl UG (haftungsbeschränkt, in formation), Erich-Kirsten-Straße 12, 10405 Berlin, Germany. By creating an account or initiating an excavation you accept these terms.</p>
            </div>

            <div>
              <div className="caps" style={{ marginBottom: 10, color: 'var(--ink)' }}>§ 2 · Uploaded files &amp; ephemeral processing</div>
              <p style={{ margin: 0 }}>Files you upload are stored temporarily in AWS S3 (EU Frankfurt). They are deleted automatically within 24 hours and immediately after your report is generated. Fossyl does not retain, copy, or analyse uploaded source code beyond what is necessary to produce the requested report. Generated HTML reports are retained according to your plan's retention window (30 days / 90 days / 1 year / unlimited).</p>
            </div>

            <div>
              <div className="caps" style={{ marginBottom: 10, color: 'var(--ink)' }}>§ 3 · AI explanations (§6 of your report)</div>
              <p style={{ margin: 0 }}>If you enable "AI plain-English explanations", Fossyl sends <strong>artifact signatures only</strong> — never full source code — to the Anthropic API. A signature contains the artifact name, type, detected coupling score, and a short structural fingerprint. No business logic, no credentials, and no comments are included. Anthropic's data usage policy applies to these requests.</p>
            </div>

            <div>
              <div className="caps" style={{ marginBottom: 10, color: 'var(--ink)' }}>§ 4 · Acceptable use</div>
              <p style={{ margin: 0 }}>You may not upload files that contain malware, files you are not authorised to analyse, or files whose analysis would violate applicable law. Automated abuse (scripted bulk uploads beyond your plan's scan quota) is prohibited. Fossyl reserves the right to suspend accounts that violate these terms without refund.</p>
            </div>

            <div>
              <div className="caps" style={{ marginBottom: 10, color: 'var(--ink)' }}>§ 5 · Billing &amp; refunds</div>
              <p style={{ margin: 0 }}>Subscriptions are billed monthly or annually via Stripe. Cancellation takes effect at the end of the current billing period; no prorated refunds are issued for unused time except where required by applicable law. The one-off Specimen plan carries a 14-day refund guarantee if the generated report is empty or corrupt — contact <span style={{ color: 'var(--bone)' }}>eri@getfossyl.dev</span>.</p>
            </div>

            <div>
              <div className="caps" style={{ marginBottom: 10, color: 'var(--ink)' }}>§ 6 · Liability</div>
              <p style={{ margin: 0 }}>The Service is provided "as is". Reports are informational and do not constitute legal, security, or architectural advice. Fossyl's liability for damages is limited to the amount paid in the 12 months preceding the claim, to the extent permitted by German law.</p>
            </div>

            <div style={{ borderTop: '1px solid var(--line)', paddingTop: 24 }}>
              <div className="caps" style={{ marginBottom: 10, color: 'var(--ink)' }}>§ 7 · Data handling addendum (GDPR)</div>
              <p style={{ margin: '0 0 10px' }}>Fossyl processes personal data as follows:</p>
              <ul style={{ margin: 0, paddingLeft: 18, display: 'flex', flexDirection: 'column', gap: 6 }}>
                <li><strong>Account data</strong> — email address, name, organisation — stored in AWS Cognito and DynamoDB (EU Frankfurt). Retained until account deletion.</li>
                <li><strong>Usage data</strong> — scan job metadata (file sizes, scan duration, artifact counts) — stored in DynamoDB (EU Frankfurt). Retained per plan.</li>
                <li><strong>Payment data</strong> — handled exclusively by Stripe; Fossyl does not store card numbers or bank details.</li>
                <li><strong>Sub-processors</strong> — AWS EMEA SARL (Frankfurt), Stripe Payments Europe Ltd (Dublin), Anthropic PBC (San Francisco, Standard Contractual Clauses apply).</li>
              </ul>
              <p style={{ margin: '10px 0 0' }}>You have the right to access, rectify, and erase your personal data. Requests: <span style={{ color: 'var(--bone)' }}>eri@getfossyl.dev</span>. Supervisory authority: Berliner Beauftragte für Datenschutz und Informationsfreiheit.</p>
            </div>

          </div>
        </div>

        <div style={{ padding: '14px 24px', borderTop: '1px solid var(--line)', display: 'flex', justifyContent: 'flex-end', gap: 10, background: 'var(--bg-1)' }}>
          <button className="btn" onClick={onDecline}>Decline</button>
          <button className="btn btn-primary" onClick={onAccept}>Accept &amp; continue →</button>
        </div>
      </div>
    </div>
  );
}

function TransferOwnershipModal({ transfer, onAccept, onDecline, accepting }) {
  return (
    <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.72)', display: 'grid', placeItems: 'center', zIndex: 400 }}>
      <div style={{ width: 480, background: 'var(--bg)', border: '1px solid var(--line-2)', borderRadius: 3, padding: 28, boxShadow: '0 20px 60px rgba(0,0,0,0.5)' }}>
        <div className="caps" style={{ color: 'var(--bone)', marginBottom: 14, fontSize: 10.5 }}>Ownership transfer</div>
        <h3 className="serif" style={{ fontSize: 26, fontWeight: 400, margin: '0 0 14px', letterSpacing: '-0.01em' }}>
          {transfer.from_name} wants to hand you their workspace.
        </h3>
        <p style={{ fontSize: 13, color: 'var(--ink-dim)', lineHeight: 1.6, margin: '0 0 8px' }}>
          Workspace: <strong style={{ color: 'var(--ink)' }}>{transfer.workspace_name}</strong>
        </p>
        <p style={{ fontSize: 13, color: 'var(--ink-dim)', lineHeight: 1.6, margin: '0 0 24px' }}>
          If you accept, you become the <strong style={{ color: 'var(--ink)' }}>Owner</strong> and {transfer.from_name} becomes a Member. If you decline, nothing changes.
        </p>
        <div className="row jc-e g-8">
          <button className="btn" onClick={onDecline} disabled={accepting}>Decline</button>
          <button className="btn btn-primary" onClick={onAccept} disabled={accepting}>
            {accepting ? 'Accepting…' : 'Accept ownership →'}
          </button>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, {
  Logo, FossilPlaceholder, Rule, Framed, SectionHeader, Stat, RiskBar, Ticks, FieldNote,
  NavContext, useNav, AuthContext, useAuth, ToastContext, useToast, ToastProvider,
  TermsModal, WorkspaceJoinModal, TransferOwnershipModal,
});
