// SERVICES — Click-to-inspect ledger. Each row expands to show a real
// artifact (prompt, code, config, conversation snippet). Earns trust the
// same way Studio Shelf does, but across every service we offer.

const SERVICES = [
  {
    n: "01",
    title: "Voice agents that pick up, listen, and convert.",
    detail: "Outbound, callback, qualification, renewal nudges. Wired into your Salesforce so every call updates the right object, the right task, the right opportunity.",
    tags: ["outbound", "voice", "twilio · livekit"],
    artifact: {
      kind: "prompt",
      label: "Real system prompt · renewal voice agent",
      body: `# Renewal Voice Agent · v2.4

You are calling a customer about an upcoming renewal.

Identity
- Your name is Iris. You are calling on behalf of {{account.name}}.
- You are an AI agent — say so if asked. Don't apologize for it.

Opener
- "Hi {{contact.first_name}}, this is Iris from Northbridge —
   you've got a renewal coming up on {{renewal.date}}. Got two minutes?"

Goals (in order)
1. Confirm renewal intent (yes / no / undecided).
2. Surface objections — pricing, seat count, missing feature.
3. Book the AE if signal; send the comparison sheet otherwise.

Never
- Quote a discount you weren't given in {{auth.discounts}}.
- Promise a feature not in {{product.shipped}}.
- Stay on the line past 4 minutes unless they're closing.

Tools available: lookup_opportunity, draft_quote, book_call, log_objection`,
    },
  },
  {
    n: "02",
    title: "Agentforce, set up properly the first time.",
    detail: "Topics, actions, prompts, and guardrails — built to your actual sales motion. We ship working agents you can demo on day 14.",
    tags: ["agentforce", "topics + actions", "flow"],
    artifact: {
      kind: "apex",
      label: "Apex tool · invocable for Agentforce",
      body: `public with sharing class GetAccountHealthTool {

    @InvocableMethod(
        label='Get Account Health'
        description='Returns a health snapshot for use in Agentforce'
        category='SupportGenie')
    public static List<Output> run(List<Input> inputs) {
        List<Output> results = new List<Output>();
        for (Input i : inputs) {
            Account a = [
                SELECT Id, Name, Health_Score__c, MRR__c,
                       Last_Activity_Date, Renewal_Date__c
                FROM Account WHERE Id = :i.accountId
                WITH SECURITY_ENFORCED
                LIMIT 1
            ];
            Output o = new Output();
            o.health = a.Health_Score__c;
            o.mrr = a.MRR__c;
            o.renewalIn = a.Renewal_Date__c != null
                ? (a.Renewal_Date__c.daysBetween(Date.today())) : null;
            results.add(o);
        }
        return results;
    }

    public class Input  { @InvocableVariable public Id accountId; }
    public class Output {
        @InvocableVariable public Decimal health;
        @InvocableVariable public Decimal mrr;
        @InvocableVariable public Integer renewalIn;
    }
}`,
    },
  },
  {
    n: "03",
    title: "Agentforce care, after the launch.",
    detail: "We monitor agent behavior weekly, tune prompts, retire the ones that drift, and add new agents as the business shifts.",
    tags: ["maintenance", "tuning", "observability"],
    artifact: {
      kind: "log",
      label: "Weekly care report · last cycle",
      body: `WEEK 18 · 2026-05-12 → 2026-05-18

agents.renewal_voice
  ✓ 412 calls, 38 bookings (9.2% — up from 7.8% w17)
  ✓ avg handle time 1m48s (target ≤ 2m)
  ⚠  3 transcripts flagged for hallucination on "tier 4"
     → patched prompt + added eval test

agents.support_triage
  ✓ 1,184 tickets, 71.4% deflected (target ≥ 65%)
  ⚠  spike in "I don't understand" Friday 14:00 → 17:00
     → traced to KB indexing job lagging behind
     → moved KB index from daily to hourly

agents.qbr_assistant
  ✓ 23 QBRs prepped, avg time-to-draft 3m12s
  → retiring, replaced by quarterly_review_v2 (in eval)

cost
  spend $1,847 (-12% w/w via routing rule update)
  forecast next week: $1,820 ± 80`,
    },
  },
  {
    n: "04",
    title: "Agent discovery for your actual business.",
    detail: "We audit how your team works today, then map the agents that would help most — ranked by value, effort, and risk.",
    tags: ["audit", "roadmap", "prioritization"],
    artifact: {
      kind: "doc",
      label: "Discovery deliverable · table of contents",
      body: `Discovery — Acme Corp · 5-day sprint

01 / Executive summary             (1 page)
02 / Today's stack & process       (3 pages)
03 / Agent candidates · ranked     (8 pages)
       3.1 Renewal voice — VALUE: high   EFFORT: med  RISK: low
       3.2 Tier-1 support — VALUE: high   EFFORT: high RISK: med
       3.3 QBR assistant — VALUE: med    EFFORT: low  RISK: low
       3.4 Field service dispatch — defer (data not ready)
       3.5 ...
04 / 90-day sequencing plan        (2 pages)
05 / Cost + ROI per agent          (3 pages)
06 / Pre-flight checklist          (1 page)
07 / Recommended first build       (1 page)

Total: 19 pages · delivered as PDF + editable doc + cost spreadsheet`,
    },
  },
  {
    n: "05",
    title: "Token math that lowers your bill.",
    detail: "We profile every prompt, swap models where appropriate, prune context, and instrument cost-per-outcome.",
    tags: ["cost", "eval", "model routing"],
    artifact: {
      kind: "yaml",
      label: "Before / after · per-turn routing",
      body: `# BEFORE: one model for everything
agent: renewal_voice
default_model: openai/gpt-realtime
# avg cost per call: $0.94

# AFTER: routed per turn
agent: renewal_voice
default_model: openai/gpt-realtime
routes:
  - when: turn.kind == "greeting"
    model: openai/gpt-realtime-mini    # ~$0.08 saved/call
  - when: turn.kind == "tool_decision"
    model: openai/gpt-realtime
  - when: turn.kind == "negotiation"
    model: anthropic/sonnet-4-realtime # better nuance, used 12% of turns
context_pruning:
  keep_last_n_turns: 6
cache:
  prompt_prefix_cache: true
# avg cost per call: $0.51  (-46%)`,
    },
  },
  {
    n: "06",
    title: "Custom Salesforce — Apex, LWC, and the rest.",
    detail: "When the platform isn't enough, we write it. Clean Apex, sensible LWC, real test coverage.",
    tags: ["apex", "lwc", "platform"],
    artifact: {
      kind: "lwc",
      label: "LWC · accountHealthCard.js (excerpt)",
      body: `import { LightningElement, api, wire } from 'lwc';
import getHealth from '@salesforce/apex/AccountHealthCtrl.getHealth';

export default class AccountHealthCard extends LightningElement {
    @api recordId;
    health;
    error;

    @wire(getHealth, { accountId: '$recordId' })
    wiredHealth({ data, error }) {
        if (data) {
            this.health = data;
            this.error = undefined;
        } else if (error) {
            this.error = error;
        }
    }

    get tone() {
        if (!this.health) return 'neutral';
        if (this.health.score >= 80) return 'good';
        if (this.health.score >= 60) return 'warn';
        return 'bad';
    }
}`,
    },
  },
  {
    n: "07",
    title: "Data plumbing that doesn't leak.",
    detail: "Mulesoft, Data Cloud, third-party event streams. We make integrations boring on purpose.",
    tags: ["mulesoft", "data cloud", "etl"],
    artifact: {
      kind: "flow",
      label: "Data Cloud · ingestion job spec",
      body: `# orders_to_datacloud.flow

source:
  type: shopify
  webhook: https://eu.shopify.com/webhooks/orders/create
  signed_with: secret_ref://shopify_webhook_secret

stage:
  type: object_store
  path: r2://supportgenie/landing/orders/
  ttl_days: 30

transform:
  rules:
    - drop: customer.email_raw      # PII reduction
    - hash: customer.id → customer.hashed_id
    - flatten: line_items[]
    - enrich:
        join: products on sku
        select: [sku, category, margin_band]

sink:
  type: salesforce_data_cloud
  dlo: order__dlo
  upsert_key: order.id
  dedup_window: 24h

observability:
  alert_if: latency_p99 > 90s
  alert_if: rows_failed_pct > 1%
  slack_channel: "#ops-pipelines"`,
    },
  },
];

function Services() {
  const [open, setOpen] = useState(null);

  return (
    <section className="section" id="services">
      <div className="shell">
        <SectionHead
          num="§ 02"
          label="WHAT WE BUILD"
          title="Seven things, done deeply."
          kicker="No service menu, no AI-strategy workshops. Click any service to see a real artifact from work we're shipping today."
        />

        <ol style={{ listStyle: "none", margin: 0, padding: 0, border: "1px solid var(--ink)" }}>
          {SERVICES.map((s, i) => (
            <ServiceRow
              key={s.n}
              s={s}
              open={open === i}
              onToggle={() => setOpen(open === i ? null : i)}
              isLast={i === SERVICES.length - 1}
            />
          ))}
        </ol>

        <div style={{ marginTop: 48, display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 16 }}>
          <p className="body" style={{ maxWidth: "50ch", margin: 0, color: "var(--ink-soft)" }}>
            Want unredacted versions of any of these under NDA? Tap a service, then ask Iris — or book the call.
          </p>
          <a href="contact.html#book" className="btn btn-ghost">Book a deep-dive <span className="arr">→</span></a>
        </div>
      </div>
    </section>
  );
}

function ServiceRow({ s, open, onToggle, isLast }) {
  return (
    <li style={{ borderBottom: isLast ? "none" : "1px solid var(--ink)" }}>
      <button
        type="button"
        onClick={onToggle}
        aria-expanded={open}
        style={{
          width: "100%",
          background: open ? "var(--paper-2)" : "transparent",
          border: "none",
          padding: "26px 28px",
          display: "grid",
          gridTemplateColumns: "60px 1fr 220px 32px",
          gap: 24,
          alignItems: "center",
          textAlign: "left",
          cursor: "pointer",
          transition: "background 0.18s",
          fontFamily: "var(--font-ui)",
          color: "inherit",
        }}
        onMouseEnter={e => { if (!open) e.currentTarget.style.background = "color-mix(in oklab, var(--ink) 4%, transparent)"; }}
        onMouseLeave={e => { if (!open) e.currentTarget.style.background = "transparent"; }}
      >
        <span className="mono" style={{ fontSize: 13, color: open ? "var(--persimmon)" : "var(--ink-mute)", letterSpacing: "0.08em" }}>{s.n}</span>
        <h3 className="serif" style={{
          margin: 0,
          fontSize: "clamp(22px, 2.6vw, 36px)",
          lineHeight: 1.05,
          letterSpacing: "-0.025em",
          color: open ? "var(--ink)" : "var(--ink-2)",
          textWrap: "balance",
          transition: "color 0.2s",
        }}>
          {s.title}
        </h3>
        <div style={{ display: "flex", flexWrap: "wrap", gap: 6, justifyContent: "flex-end" }}>
          {s.tags.map(t => (
            <span key={t} className="mono" style={{
              fontSize: 10,
              padding: "4px 8px",
              border: "1px solid var(--rule-strong)",
              color: "var(--ink-soft)",
              letterSpacing: "0.06em",
              textTransform: "lowercase",
            }}>{t}</span>
          ))}
        </div>
        <span style={{
          width: 28,
          height: 28,
          border: "1px solid var(--ink)",
          display: "inline-grid",
          placeItems: "center",
          fontFamily: "var(--font-mono)",
          fontSize: 16,
          color: open ? "var(--persimmon)" : "var(--ink)",
          background: open ? "var(--ink)" : "transparent",
          transition: "all 0.18s",
        }}>
          {open ? "−" : "+"}
        </span>
      </button>

      {/* Expanded panel — uses grid-template-rows 1fr/0fr trick for reliable animation */}
      <div style={{
        display: "grid",
        gridTemplateRows: open ? "1fr" : "0fr",
        transition: "grid-template-rows 0.5s cubic-bezier(.2,.7,.2,1)",
      }}>
        <div style={{ overflow: "hidden", minHeight: 0 }}>
          <div style={{
            padding: open ? "0 28px 28px 28px" : "0 28px",
            background: "var(--paper-2)",
            display: "grid",
            gridTemplateColumns: "60px 1fr 1.4fr 32px",
            gap: 24,
            alignItems: "start",
          }}>
            <span></span>
            <div>
              <p className="body" style={{ margin: 0, color: "var(--ink-soft)", maxWidth: "44ch" }}>
                {s.detail}
              </p>
            </div>
            <ArtifactView artifact={s.artifact} />
            <span></span>
          </div>
        </div>
      </div>
    </li>
  );
}

function ArtifactView({ artifact }) {
  return (
    <div style={{
      border: "1px solid var(--ink)",
      background: "#1a1d22",
      color: "#f0eddf",
      overflow: "hidden",
    }}>
      <div style={{
        padding: "10px 16px",
        borderBottom: "1px solid color-mix(in oklab, #f0eddf 14%, transparent)",
        display: "flex",
        justifyContent: "space-between",
        alignItems: "center",
        fontFamily: "var(--font-mono)",
        fontSize: 10,
        color: "color-mix(in oklab, #f0eddf 65%, transparent)",
        letterSpacing: "0.12em",
        textTransform: "uppercase",
      }}>
        <span>↳ {artifact.label}</span>
        <span style={{ color: "var(--persimmon)" }}>● real</span>
      </div>
      <pre style={{
        margin: 0,
        padding: "18px 20px",
        fontFamily: "var(--font-mono)",
        fontSize: 11.5,
        lineHeight: 1.55,
        color: "#f0eddf",
        overflowX: "auto",
        maxHeight: 360,
        whiteSpace: "pre-wrap",
        wordBreak: "break-word",
      }}>
        <ArtifactCode body={artifact.body} kind={artifact.kind} />
      </pre>
    </div>
  );
}

// Light syntax tinting — keyword, string, comment per common shape
function ArtifactCode({ body, kind }) {
  const lines = body.split("\n");
  return lines.map((line, i) => {
    // Comment-like (starts with # or //)
    if (/^\s*(#|\/\/)/.test(line)) {
      return <div key={i} style={{ color: "color-mix(in oklab, #f0eddf 50%, transparent)" }}>{line}</div>;
    }
    // Markdown header (# Foo, ## Foo)
    if (/^#+\s/.test(line)) {
      return <div key={i} style={{ color: "#d4a017", fontWeight: 600 }}>{line}</div>;
    }
    // Lines like "key: value"
    const m = line.match(/^(\s*-?\s*)([a-zA-Z_][\w]*)(\s*:)(.*)$/);
    if (m) {
      return (
        <div key={i}>
          {m[1]}<span style={{ color: "#d4a017" }}>{m[2]}</span><span style={{ opacity: 0.6 }}>{m[3]}</span><span>{m[4]}</span>
        </div>
      );
    }
    // Apex/LWC-style: highlight keywords
    if (kind === "apex" || kind === "lwc") {
      const keywords = ["public", "private", "static", "class", "return", "for", "if", "else", "new", "with", "from", "import", "export", "default", "get", "set", "this", "true", "false"];
      const parts = line.split(/(\b\w+\b)/);
      return (
        <div key={i}>
          {parts.map((p, j) =>
            keywords.includes(p)
              ? <span key={j} style={{ color: "#c8421e" }}>{p}</span>
              : <span key={j}>{p}</span>
          )}
        </div>
      );
    }
    return <div key={i}>{line}</div>;
  });
}

Object.assign(window, { Services, SERVICES });
