// ---------- n8n Developer Control Panel ----------

const N8N_EVENTS = [
  "call.completed", "call.escalated", "appointment.booked",
  "lead.captured", "payment.promised", "custom.event",
];

const DEFAULT_PAYLOAD = {
  phoneNumber: "60123456789",
  contactId: "contact_demo_001",
  roomName: "voxpro-room-001",
  transcript: "Customer requested a callback tomorrow at 3 PM.",
  reply: "Confirmed, we will arrange that for you.",
  callDurationMs: 180000,
  outcome: "follow_up_required",
};

// ── Trigger event metadata ────────────────────────────────────────────────
const TRIGGER_META = {
  "call.completed": {
    icon: "check",
    label: "Call completed",
    color: "var(--ok)",
    useCase: "Send summary, log to CRM, close ticket",
    when: "The call ends successfully",
    snippet: `When the conversation concludes, include [[automation:call.completed]] at the end of your final response.`,
    example: `Thank you for calling, have a great day. [[automation:call.completed]]`,
  },
  "call.escalated": {
    icon: "phone",
    label: "Call escalated",
    color: "#f59e0b",
    useCase: "Alert human agent, create support ticket, notify team",
    when: "Customer requests a human agent or supervisor",
    snippet: `When the customer asks to speak with a human agent or supervisor, include [[automation:call.escalated]] in your response.`,
    example: `I'll transfer you to a human agent now. Please hold. [[automation:call.escalated]]`,
  },
  "appointment.booked": {
    icon: "calendar",
    label: "Appointment booked",
    color: "#6366f1",
    useCase: "Add to calendar, send confirmation SMS/email, notify staff",
    when: "Customer confirms a booking or appointment time",
    snippet: `When the customer confirms an appointment date and time, include [[automation:appointment.booked]] in your response.`,
    example: `Great, you're booked for Thursday at 2 PM. See you then! [[automation:appointment.booked]]`,
  },
  "lead.captured": {
    icon: "user",
    label: "Lead captured",
    color: "#8b5cf6",
    useCase: "Create CRM contact, notify sales, start drip campaign",
    when: "Customer expresses clear interest or requests follow-up",
    snippet: `When the customer shows clear interest or requests a callback or more information, include [[automation:lead.captured]] in your response.`,
    example: `I'll have a specialist reach out to you within 24 hours. [[automation:lead.captured]]`,
  },
  "payment.promised": {
    icon: "wallet",
    label: "Payment promised",
    color: "#10b981",
    useCase: "Log commitment, schedule payment reminder, update debt record",
    when: "Customer commits to a specific payment date",
    snippet: `When the customer commits to a payment date, include [[automation:payment.promised]] in your response.`,
    example: `I've noted your payment commitment for the 15th. We'll send a reminder. [[automation:payment.promised]]`,
  },
  "custom.event": {
    icon: "settings",
    label: "Custom event",
    color: "var(--a)",
    useCase: "Any custom workflow — fully flexible",
    when: "Any condition you define in the system prompt",
    snippet: `When [describe your condition here], include [[automation:custom.event]] in your response.`,
    example: `Your request has been logged and our team will follow up. [[automation:custom.event]]`,
  },
};

const MASTER_SNIPPET = `## Automation triggers
When certain outcomes occur during the conversation, include the matching trigger token in your response. These tokens are silent — the customer never hears them.

Available triggers:
- [[automation:appointment.booked]] — customer confirmed an appointment
- [[automation:lead.captured]] — customer expressed interest or requested follow-up
- [[automation:payment.promised]] — customer committed to a payment date
- [[automation:call.escalated]] — customer requested a human agent
- [[automation:call.completed]] — successful end of call
- [[automation:custom.event]] — [replace with your custom condition]

Rules:
- Include the token at the end of the sentence where the outcome occurs
- Only use one trigger token per response turn
- Never explain the token to the customer`;

// ── Automation guide component ─────────────────────────────────────────────
function AutomationsGuide({ automations, n8nAutomations, autoLoading, showNewAuto, setShowNewAuto, newAuto, setNewAuto, saveAutomation, deleteAutomation, setTab, setFireEvent, assistantList }) {
  const [guideEvent, setGuideEvent] = React.useState("call.completed");
  const [copied, setCopied] = React.useState(null);
  const [payloadOpen, setPayloadOpen] = React.useState(false);

  function copy(text, key) {
    navigator.clipboard.writeText(text).then(() => {
      setCopied(key);
      setTimeout(() => setCopied(null), 2000);
    });
  }

  const meta = TRIGGER_META[guideEvent] || TRIGGER_META["custom.event"];

  const SAMPLE_PAYLOAD = {
    meta: {
      automationId: "auto_abc123",
      automationName: "Send WhatsApp after call",
      triggerEvent: guideEvent,
      occurredAt: new Date().toISOString(),
    },
    data: {
      phoneNumber: "+60123456789",
      transcript: "Customer confirmed appointment for Thursday 2pm.",
      reply: "Great, you're booked for Thursday at 2 PM. [[automation:" + guideEvent + "]]",
      outcome: guideEvent,
      expectedPaymentDate: guideEvent === "payment.promised" ? "2026-06-20" : null,
      callDurationMs: 64000,
    },
    assistant: { id: "asst_xyz", name: "Booking Assistant" },
    resolvedMatchContext: {
      matchedPhoneNumber: "+60123456789",
      matchedName: "Ahmad Fauzi",
      matchedNric: null,
    },
  };

  return (
    <div style={{ display: "grid", gridTemplateColumns: "1fr 340px", gap: 16, alignItems: "start" }}>

      {/* ── Left column ── */}
      <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>

        {/* Concept banner */}
        <div style={{ background: "rgba(99,102,241,.08)", border: "1px solid rgba(99,102,241,.22)", borderRadius: 9, padding: "10px 14px", fontSize: 12, color: "var(--c-text-2)", display: "flex", gap: 10, alignItems: "flex-start" }}>
          <Icon name="workflow" style={{ color: "#6366f1", flexShrink: 0, marginTop: 1 }} />
          <span>
            <strong style={{ color: "var(--c-text)" }}>How it works:</strong> Your voice AI includes a silent trigger token in its reply →
            VoxPro detects it → fires the matching n8n webhook → your n8n workflow runs (send WhatsApp, update CRM, book calendar, etc.)
          </span>
        </div>

        {/* Automations list */}
        <Card title="Configured automations" action={
          <Btn variant="primary" size="sm" icon="plus" onClick={() => setShowNewAuto(true)}>New automation</Btn>
        }>
          {autoLoading && <div className="muted text-12" style={{ padding: "12px 0" }}>Loading…</div>}
          {!autoLoading && automations.length === 0 && (
            <div className="empty-state" style={{ padding: "20px 0" }}>
              <div className="empty-state-title">No automations yet</div>
              <div>Add your first automation below to connect n8n to your voice assistant.</div>
            </div>
          )}
          {!autoLoading && automations.length > 0 && (
            <div style={{ display: "flex", flexDirection: "column", gap: 1 }}>
              {automations.map(a => {
                const m = TRIGGER_META[a.triggerEvent] || {};
                return (
                  <div key={a.id} style={{ display: "flex", alignItems: "center", gap: 10, padding: "10px 0", borderBottom: "1px solid var(--c-line)" }}>
                    <div style={{ width: 8, height: 8, borderRadius: "50%", background: a.enabled !== false ? "var(--ok)" : "var(--c-text-4)", flexShrink: 0 }} />
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div className="row gap-2">
                        <span className="strong text-13">{a.name}</span>
                        <span className="chip"><span className="mono text-11">{a.triggerEvent}</span></span>
                      </div>
                      <div className="mono text-11 muted truncate">{a.webhookUrl}</div>
                    </div>
                    <div className="row gap-2">
                      <Btn variant="ghost" size="sm" icon="play" onClick={() => { setTab("fire"); setFireEvent(a.triggerEvent); }}>Test</Btn>
                      <Btn variant="ghost" size="sm" icon="trash" style={{ color: "var(--err)" }} onClick={() => deleteAutomation(a.id)} />
                    </div>
                  </div>
                );
              })}
            </div>
          )}
        </Card>

        {/* New automation inline form */}
        {showNewAuto && (
          <Card title="New automation">
            <Field label="Name">
              <Input value={newAuto.name} onChange={e => setNewAuto(d => ({ ...d, name: e.target.value }))} placeholder="e.g. Send WhatsApp after appointment booked" />
            </Field>
            <Field label="n8n Webhook URL" help="In n8n: open your workflow → click the Webhook node → copy the Test URL or Production URL">
              <Input value={newAuto.webhookUrl} onChange={e => setNewAuto(d => ({ ...d, webhookUrl: e.target.value }))} placeholder="http://localhost:5678/webhook/xxxxxxxx" />
            </Field>
            <Field label="Trigger event" help="The AI includes [[automation:this-event]] in its reply to fire this webhook">
              <Select value={newAuto.triggerEvent} onChange={e => setNewAuto(d => ({ ...d, triggerEvent: e.target.value }))}>
                {N8N_EVENTS.map(ev => <option key={ev} value={ev}>{ev} — {TRIGGER_META[ev]?.useCase || ""}</option>)}
              </Select>
            </Field>
            <Field label="Bill client per send" help="If this automation actually sends a WhatsApp/SMS, log a billable event at the client's configured rate each time it fires successfully">
              <Select value={newAuto.billingCategory || ""} onChange={e => setNewAuto(d => ({ ...d, billingCategory: e.target.value }))}>
                <option value="">Not billed</option>
                <option value="whatsapp_marketing">WhatsApp — marketing template</option>
                <option value="whatsapp_utility">WhatsApp — utility/OTP template</option>
                <option value="sms">SMS</option>
              </Select>
            </Field>
            <Field label="Limit to assistants" help="Leave blank to fire for all assistants. Select one or more to restrict.">
              <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
                {assistantList.length === 0 && <span className="muted text-12">No assistants found</span>}
                {assistantList.map(a => (
                  <label key={a.id} style={{ display: "flex", alignItems: "center", gap: 8, fontSize: 13, cursor: "pointer" }}>
                    <input type="checkbox" checked={(newAuto.assistantIds || []).includes(a.id)}
                      onChange={e => setNewAuto(d => ({
                        ...d,
                        assistantIds: e.target.checked
                          ? [...(d.assistantIds || []), a.id]
                          : (d.assistantIds || []).filter(id => id !== a.id),
                      }))} />
                    {a.name || a.displayName || a.id}
                    {a.useCase && <span className="muted text-11">— {a.useCase}</span>}
                  </label>
                ))}
                {(newAuto.assistantIds || []).length > 0 && (
                  <span className="text-11" style={{ color: "var(--c-accent)" }}>
                    Only fires for {(newAuto.assistantIds || []).length} selected assistant(s)
                  </span>
                )}
              </div>
            </Field>
            {newAuto.triggerEvent && (
              <div style={{ background: "var(--c-surface)", border: "1px solid var(--c-line)", borderRadius: 7, padding: "10px 12px", fontSize: 11 }}>
                <div className="row gap-2 mb-2" style={{ justifyContent: "space-between" }}>
                  <span className="strong text-12">System prompt snippet to add to your assistant</span>
                  <Btn variant="ghost" size="sm" onClick={() => copy(TRIGGER_META[newAuto.triggerEvent]?.snippet || "", "form-snippet")}>
                    {copied === "form-snippet" ? "Copied!" : "Copy"}
                  </Btn>
                </div>
                <pre style={{ margin: 0, fontFamily: "var(--f-mono)", fontSize: 11, color: "var(--c-text-2)", whiteSpace: "pre-wrap", lineHeight: 1.6 }}>
                  {TRIGGER_META[newAuto.triggerEvent]?.snippet}
                </pre>
              </div>
            )}
            <div className="row gap-2 mt-2">
              <Btn variant="primary" onClick={saveAutomation} disabled={!newAuto.name || !newAuto.webhookUrl}>Save automation</Btn>
              <Btn variant="ghost" onClick={() => setShowNewAuto(false)}>Cancel</Btn>
            </div>
          </Card>
        )}

        {/* System prompt snippets */}
        <Card title="System prompt snippets" subtitle="Add these to your assistant's system prompt to enable each trigger">
          {/* Master snippet */}
          <div style={{ background: "rgba(99,102,241,.07)", border: "1px solid rgba(99,102,241,.2)", borderRadius: 8, padding: "10px 12px", marginBottom: 14 }}>
            <div className="row gap-2 mb-2" style={{ justifyContent: "space-between", alignItems: "center" }}>
              <span className="strong text-12" style={{ color: "#818cf8" }}>Master snippet — enables all triggers at once</span>
              <Btn variant="ghost" size="sm" onClick={() => copy(MASTER_SNIPPET, "master")}>
                {copied === "master" ? "Copied!" : "Copy all"}
              </Btn>
            </div>
            <pre style={{ margin: 0, fontFamily: "var(--f-mono)", fontSize: 10.5, color: "var(--c-text-3)", whiteSpace: "pre-wrap", lineHeight: 1.7, maxHeight: 140, overflowY: "auto" }}>
              {MASTER_SNIPPET}
            </pre>
          </div>

          {/* Per-event snippets */}
          <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
            {N8N_EVENTS.map(ev => {
              const m = TRIGGER_META[ev];
              return (
                <div key={ev} style={{ border: "1px solid var(--c-line)", borderRadius: 8, overflow: "hidden" }}>
                  <div style={{ display: "flex", alignItems: "center", gap: 10, padding: "9px 12px", background: "var(--c-surface)", borderBottom: "1px solid var(--c-line)" }}>
                    <span style={{ width: 8, height: 8, borderRadius: "50%", background: m.color, flexShrink: 0 }} />
                    <span className="strong text-12" style={{ flex: 1 }}>{m.label}</span>
                    <span className="muted text-11">{m.useCase}</span>
                    <Btn variant="ghost" size="sm" onClick={() => copy(m.snippet, ev)}>
                      {copied === ev ? "Copied!" : "Copy"}
                    </Btn>
                  </div>
                  <div style={{ padding: "8px 12px" }}>
                    <div className="muted text-11 mb-1">Snippet</div>
                    <pre style={{ margin: 0, fontFamily: "var(--f-mono)", fontSize: 11, color: "var(--c-text-2)", whiteSpace: "pre-wrap", lineHeight: 1.6 }}>{m.snippet}</pre>
                    <div className="muted text-11 mt-2 mb-1">Example AI response</div>
                    <pre style={{ margin: 0, fontFamily: "var(--f-mono)", fontSize: 11, color: "var(--c-text-3)", whiteSpace: "pre-wrap", lineHeight: 1.6, fontStyle: "italic" }}>{m.example}</pre>
                  </div>
                </div>
              );
            })}
          </div>
        </Card>

        {/* Payload reference */}
        <Card
          title="Payload reference"
          subtitle="Exact JSON your n8n webhook receives"
          action={<Btn variant="ghost" size="sm" onClick={() => setPayloadOpen(o => !o)}>{payloadOpen ? "Hide" : "Show"}</Btn>}
        >
          <div className="muted text-12 mb-2">Select event to preview payload:</div>
          <Select value={guideEvent} onChange={e => setGuideEvent(e.target.value)} style={{ marginBottom: 12 }}>
            {N8N_EVENTS.map(ev => <option key={ev} value={ev}>{ev}</option>)}
          </Select>
          {payloadOpen && (
            <div>
              <div className="row gap-2 mb-2" style={{ justifyContent: "space-between" }}>
                <span className="muted text-11">n8n receives this JSON on every trigger</span>
                <Btn variant="ghost" size="sm" onClick={() => copy(JSON.stringify(SAMPLE_PAYLOAD, null, 2), "payload")}>
                  {copied === "payload" ? "Copied!" : "Copy JSON"}
                </Btn>
              </div>
              <pre className="code" style={{ fontSize: 11, maxHeight: 400, overflowY: "auto" }}>
                <code>{JSON.stringify(SAMPLE_PAYLOAD, null, 2)}</code>
              </pre>
              <div style={{ marginTop: 14, display: "flex", flexDirection: "column", gap: 6 }}>
                {[
                  { path: "meta.triggerEvent", desc: "Which event fired (e.g. appointment.booked)" },
                  { path: "meta.occurredAt", desc: "ISO timestamp of when the trigger fired" },
                  { path: "data.phoneNumber", desc: "Customer phone number (+60 format)" },
                  { path: "data.transcript", desc: "Full conversation text up to this point" },
                  { path: "data.reply", desc: "The AI's response that contained the trigger token" },
                  { path: "data.outcome", desc: "Same as triggerEvent — use in n8n conditions" },
                  { path: "data.expectedPaymentDate", desc: "ISO date if a payment was promised (null otherwise)" },
                  { path: "data.callDurationMs", desc: "Call duration in milliseconds" },
                  { path: "assistant.name", desc: "Which VoxPro assistant triggered this" },
                  { path: "resolvedMatchContext.matchedPhoneNumber", desc: "Normalised phone matched against your dataset" },
                  { path: "resolvedMatchContext.matchedName", desc: "Customer name from matched CSV row (if any)" },
                ].map(r => (
                  <div key={r.path} style={{ display: "flex", gap: 10, fontSize: 11 }}>
                    <code className="mono" style={{ color: "var(--a)", minWidth: 220, flexShrink: 0 }}>{r.path}</code>
                    <span className="muted">{r.desc}</span>
                  </div>
                ))}
              </div>
            </div>
          )}
        </Card>
      </div>

      {/* ── Right column: sticky setup guide ── */}
      <div style={{ position: "sticky", top: 16, display: "flex", flexDirection: "column", gap: 12 }}>
        <Card title="Setup guide" subtitle="Wire n8n to your voice assistant in 4 steps">
          {[
            {
              n: 1, title: "Create a webhook in n8n",
              body: "Open n8n → New workflow → search for Webhook → set Method to POST → click Listen for test event → copy the Test URL.",
            },
            {
              n: 2, title: "Add automation in VoxPro",
              body: "Click New automation above → paste the webhook URL → choose your trigger event → save.",
            },
            {
              n: 3, title: "Add trigger to assistant system prompt",
              body: "Open the assistant → Prompt tab → paste the matching snippet from the left. The AI will include the token when the condition is met.",
            },
            {
              n: 4, title: "Test the full flow",
              body: "Go to the Test Fire tab → fire the event → check n8n for the incoming request → build the rest of your workflow from there.",
            },
          ].map(s => (
            <div key={s.n} style={{ display: "flex", gap: 10, padding: "10px 0", borderBottom: "1px solid var(--c-line)" }}>
              <div style={{ width: 22, height: 22, borderRadius: "50%", background: "var(--a)", color: "#0c0c14", display: "grid", placeItems: "center", fontSize: 11, fontWeight: 700, flexShrink: 0 }}>{s.n}</div>
              <div>
                <div className="strong text-12" style={{ marginBottom: 3 }}>{s.title}</div>
                <div className="muted text-11" style={{ lineHeight: 1.6 }}>{s.body}</div>
              </div>
            </div>
          ))}
        </Card>

        <Card title="How the AI triggers n8n">
          <div className="muted text-12" style={{ lineHeight: 1.7 }}>
            <p style={{ margin: "0 0 8px" }}>You add a token like <code className="mono" style={{ fontSize: 11 }}>[[automation:appointment.booked]]</code> to the assistant's system prompt instructions.</p>
            <p style={{ margin: "0 0 8px" }}>When the AI detects that condition is met, it includes the token silently in its reply. <strong style={{ color: "var(--c-text)" }}>The customer never hears it.</strong></p>
            <p style={{ margin: 0 }}>VoxPro strips the token before TTS, detects it, and fires the matching webhook to n8n immediately.</p>
          </div>
        </Card>
      </div>

    </div>
  );
}
const StatusDot = ({ online }) => (
  <span style={{
    display: "inline-block", width: 8, height: 8, borderRadius: "50%",
    background: online ? "var(--ok)" : "var(--err)",
    boxShadow: online ? "0 0 0 3px rgba(34,197,94,.18)" : "none",
    marginRight: 6, flexShrink: 0,
  }} />
);

// ── Main view ─────────────────────────────────────────────────────────────
const N8nView = () => {
  const [tab, setTab] = React.useState("status");
  const [status, setStatus] = React.useState(null);
  const [workflows, setWorkflows] = React.useState(null);
  const [wfLoading, setWfLoading] = React.useState(false);
  const [executions, setExecutions] = React.useState(null);
  const [exLoading, setExLoading] = React.useState(false);
  const [automations, setAutomations] = React.useState([]);
  const [autoLoading, setAutoLoading] = React.useState(false);
  const [showNewAuto, setShowNewAuto] = React.useState(false);
  const [newAuto, setNewAuto] = React.useState({ name: "", webhookUrl: "", triggerEvent: "call.completed", assistantIds: [], billingCategory: "" });
  const [assistantList, setAssistantList] = React.useState([]);
  const [fireEvent, setFireEvent] = React.useState("call.completed");
  const [firePayload, setFirePayload] = React.useState(JSON.stringify(DEFAULT_PAYLOAD, null, 2));
  const [firing, setFiring] = React.useState(false);
  const [fireResult, setFireResult] = React.useState(null);
  const [starting, setStarting] = React.useState(false);
  const [managed, setManaged] = React.useState(false);

  // Poll status every 8s (faster while starting)
  React.useEffect(() => {
    function poll() {
      voxApiOptional("/n8n/status", { online: false, url: "http://localhost:5678", hasApiKey: false })
        .then(s => {
          setStatus(s);
          if (s.online && starting) setStarting(false);
        });
    }
    poll();
    const id = setInterval(poll, starting ? 2000 : 8000);
    return () => clearInterval(id);
  }, [starting]);

  // Sync managed state on mount
  React.useEffect(() => {
    voxApiOptional("/n8n/process", { managed: false }).then(d => setManaged(!!d.managed));
    voxApiOptional("/assistants", { assistants: [] }).then(d => setAssistantList(Array.isArray(d.assistants) ? d.assistants : []));
  }, []);

  async function startN8n() {
    setStarting(true);
    try {
      await voxApiOptional("/n8n/start", { ok: false }, { method: "POST" });
      setManaged(true);
    } catch { setStarting(false); }
  }

  async function stopN8n() {
    await voxApiOptional("/n8n/stop", {}, { method: "POST" });
    setManaged(false);
    setStatus(s => s ? { ...s, online: false } : s);
  }

  // Load workflows when tab switches to workflows
  React.useEffect(() => {
    if (tab !== "workflows" || workflows !== null) return;
    setWfLoading(true);
    voxApiOptional("/n8n/workflows", null)
      .then(d => setWorkflows(d))
      .finally(() => setWfLoading(false));
  }, [tab]);

  // Load executions when tab switches to executions
  React.useEffect(() => {
    if (tab !== "executions") return;
    setExLoading(true);
    voxApiOptional("/n8n/executions?limit=30", null)
      .then(d => setExecutions(d))
      .finally(() => setExLoading(false));
  }, [tab]);

  // Load automations (VoxPro webhooks)
  React.useEffect(() => {
    if (tab !== "automations" && tab !== "fire") return;
    if (automations.length) return;
    setAutoLoading(true);
    voxApiOptional("/automations/config", { automations: [] })
      .then(d => setAutomations(d.automations || []))
      .finally(() => setAutoLoading(false));
  }, [tab]);

  async function toggleWorkflow(wf) {
    try {
      const updated = await voxApi(`/n8n/workflows/${wf.id}`, {
        method: "PATCH",
        body: JSON.stringify({ active: !wf.active }),
      });
      setWorkflows(prev => ({
        ...prev,
        data: (prev.data || []).map(w => w.id === wf.id ? { ...w, active: updated.active } : w),
      }));
    } catch (e) {
      alert("Failed to toggle workflow: " + e.message);
    }
  }

  async function saveAutomation() {
    if (!newAuto.name || !newAuto.webhookUrl) return;
    try {
      const saved = await voxApi("/automations/config", { method: "POST", body: JSON.stringify(newAuto) });
      setAutomations(a => [...a, saved.automation || saved]);
      setShowNewAuto(false);
      setNewAuto({ name: "", webhookUrl: "", triggerEvent: "call.completed", assistantIds: [], billingCategory: "" });
    } catch (e) { alert("Save failed: " + e.message); }
  }

  async function deleteAutomation(id) {
    if (!confirm("Delete this automation?")) return;
    try {
      await voxApi(`/automations/config/${id}`, { method: "DELETE" });
      setAutomations(a => a.filter(x => x.id !== id));
    } catch (e) { alert("Delete failed: " + e.message); }
  }

  async function fireEventNow() {
    let parsed = {};
    try { parsed = JSON.parse(firePayload); } catch { alert("Invalid JSON payload"); return; }
    setFiring(true);
    setFireResult(null);
    try {
      const res = await voxApi("/automations/trigger", {
        method: "POST",
        body: JSON.stringify({ triggerEvent: fireEvent, payload: parsed }),
      });
      setFireResult(res);
    } catch (e) {
      setFireResult({ error: e.message });
    } finally {
      setFiring(false);
    }
  }

  function refreshWorkflows() {
    setWorkflows(null);
    setWfLoading(true);
    voxApiOptional("/n8n/workflows", null)
      .then(d => setWorkflows(d))
      .finally(() => setWfLoading(false));
  }

  function refreshExecutions() {
    setExLoading(true);
    voxApiOptional("/n8n/executions?limit=30", null)
      .then(d => setExecutions(d))
      .finally(() => setExLoading(false));
  }

  const n8nAutomations = automations.filter(a =>
    a.webhookUrl && (a.webhookUrl.includes("localhost:5678") || a.webhookUrl.includes("webhook/"))
  );

  return (
    <div>
      {/* Page header */}
      <div className="ph">
        <div className="row gap-3" style={{ alignItems: "center" }}>
          <div style={{ width: 36, height: 36, borderRadius: 9, background: "var(--c-surface)", border: "1px solid var(--c-line-2)", display: "grid", placeItems: "center" }}>
            <Icon name="workflow" />
          </div>
          <div>
            <h1 className="ph-title">n8n Automation</h1>
            <div className="ph-subtitle">
              {status === null ? "Checking connection…" : (
                <span className="row gap-1" style={{ alignItems: "center", display: "inline-flex" }}>
                  <StatusDot online={status.online} />
                  {status.online ? `Connected · ${status.url}` : `Offline · ${status.url}`}
                  {status.online && !status.hasApiKey && (
                    <span className="badge badge-warn" style={{ marginLeft: 8 }}>No API key — workflows unavailable</span>
                  )}
                </span>
              )}
            </div>
          </div>
        </div>
        <div className="ph-actions">
          {status?.online && (
            <Btn variant="ghost" size="sm" icon="globe" onClick={() => window.open("http://47.250.58.1:5678/workflow/5rKUFgZT6NMggwAb", "_blank")}>
              Open n8n UI
            </Btn>
          )}
        </div>
      </div>

      <Tabs
        tabs={[
          { id: "status",     label: "Overview" },
          { id: "workflows",  label: "Workflows" },
          { id: "executions", label: "Executions" },
          { id: "automations",label: "VoxPro → n8n" },
          { id: "fire",       label: "Test Fire" },
        ]}
        active={tab}
        onSelect={setTab}
      />

      <div style={{ marginTop: 16 }}>

        {/* ── Overview ───────────────────────────────────────────── */}
        {tab === "status" && (
          <div className="g-2">
            <Card title="Connection">
              <div className="col" style={{ gap: 12 }}>
                <div style={{ display: "grid", gridTemplateColumns: "120px 1fr", gap: "8px 0", fontSize: 13, alignItems: "center" }}>
                  <span className="muted text-12">Status</span>
                  <span className="row gap-2" style={{ alignItems: "center" }}>
                    <StatusDot online={status?.online} />
                    <span className="strong">{status === null ? "Checking…" : status.online ? "Online" : "Offline"}</span>
                  </span>
                  <span className="muted text-12">URL</span>
                  <span className="mono text-12">{status?.url || "http://localhost:5678"}</span>
                  <span className="muted text-12">API Key</span>
                  <span>
                    {status?.hasApiKey
                      ? <Badge tone="ok" dot>configured</Badge>
                      : <Badge tone="warn" dot>not set</Badge>}
                  </span>
                </div>

                {!status?.online && (
                  <div style={{ background: "var(--c-surface)", border: "1px solid var(--c-line)", borderRadius: 8, padding: "12px 14px", fontSize: 12 }}>
                    <div className="strong" style={{ marginBottom: 10 }}>Start n8n</div>
                    {starting ? (
                      <div className="row gap-2" style={{ alignItems: "center", color: "var(--c-muted)" }}>
                        <span className="spinner" style={{ width: 14, height: 14, border: "2px solid var(--c-line)", borderTopColor: "var(--a)", borderRadius: "50%", animation: "spin 0.7s linear infinite", flexShrink: 0 }} />
                        <span>Starting n8n… this can take 10–20 seconds</span>
                      </div>
                    ) : (
                      <div className="col" style={{ gap: 8 }}>
                        <Btn variant="primary" size="sm" icon="play" onClick={startN8n}>Start n8n</Btn>
                        <div className="muted text-11">Or run manually in a terminal: <code className="mono">npx n8n</code></div>
                      </div>
                    )}
                  </div>
                )}

                {status?.online && managed && (
                  <Btn variant="ghost" size="sm" icon="x" style={{ color: "var(--err)" }} onClick={stopN8n}>Stop n8n</Btn>
                )}

                {status?.online && !status?.hasApiKey && (
                  <div style={{ background: "rgba(251,191,36,.07)", border: "1px solid rgba(251,191,36,.3)", borderRadius: 8, padding: "10px 13px", fontSize: 12 }}>
                    <div className="strong" style={{ marginBottom: 4 }}>Add API key to unlock Workflows tab</div>
                    <ol style={{ paddingLeft: 16, lineHeight: 1.8, color: "var(--c-muted)" }}>
                      <li>Open n8n UI → Settings → n8n API</li>
                      <li>Click <strong>Create an API Key</strong>, copy it</li>
                      <li>Add <code className="mono" style={{ fontSize: 11 }}>N8N_API_KEY=your_key</code> to your <code className="mono" style={{ fontSize: 11 }}>.env</code></li>
                      <li>Restart VoxPro backend</li>
                    </ol>
                  </div>
                )}
              </div>
            </Card>

            <Card title="Quick start — receive VoxPro events in n8n">
              <ol style={{ paddingLeft: 16, lineHeight: 2, fontSize: 12.5, color: "var(--c-muted)" }}>
                <li>In n8n, create a new workflow</li>
                <li>Add a <strong style={{ color: "var(--c-text)" }}>Webhook</strong> trigger node → copy the webhook URL</li>
                <li>Go to <strong style={{ color: "var(--c-text)" }}>VoxPro → n8n tab → Automations</strong> → New automation</li>
                <li>Paste the n8n webhook URL, choose a trigger event (e.g. <code className="mono" style={{ fontSize: 11 }}>call.completed</code>)</li>
                <li>Use the <strong style={{ color: "var(--c-text)" }}>Test Fire</strong> tab to send a test event — verify it arrives in n8n</li>
                <li>Build your n8n workflow from there (send WhatsApp, update CRM, etc.)</li>
              </ol>
            </Card>

            <Card title="VoxPro trigger events" subtitle="Events your workflows can subscribe to">
              <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
                {N8N_EVENTS.map(ev => (
                  <span key={ev} className="chip"><span className="mono text-11">{ev}</span></span>
                ))}
              </div>
            </Card>

            <Card title="Environment variables">
              <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8, fontSize: 12 }}>
                {[
                  { k: "N8N_URL", v: "http://localhost:5678", desc: "n8n base URL" },
                  { k: "N8N_API_KEY", v: "— not set —", desc: "For Workflows + Executions tabs" },
                ].map(row => (
                  <div key={row.k} style={{ background: "var(--c-surface)", borderRadius: 7, padding: "8px 10px" }}>
                    <div className="mono text-11" style={{ color: "var(--c-accent)" }}>{row.k}</div>
                    <div className="mono text-11 muted" style={{ marginTop: 2 }}>{row.v}</div>
                    <div className="text-11 muted" style={{ marginTop: 3 }}>{row.desc}</div>
                  </div>
                ))}
              </div>
            </Card>
          </div>
        )}

        {/* ── Workflows ──────────────────────────────────────────── */}
        {tab === "workflows" && (
          <div>
            <div style={{ display: "flex", justifyContent: "flex-end", marginBottom: 12 }}>
              <Btn variant="ghost" size="sm" icon="activity" onClick={refreshWorkflows}>Refresh</Btn>
            </div>

            {wfLoading && (
              <Card><div className="empty-state"><div className="muted">Loading workflows…</div></div></Card>
            )}

            {!wfLoading && workflows === null && (
              <Card>
                <div className="empty-state">
                  <div className="empty-state-title">Could not load workflows</div>
                  <div>n8n may be offline or API key is missing.</div>
                </div>
              </Card>
            )}

            {!wfLoading && workflows?.error && (
              <Card>
                <div className="empty-state">
                  <div className="empty-state-title" style={{ color: "var(--err)" }}>{workflows.error}</div>
                  <div className="muted text-12 mt-1">Set <code className="mono">N8N_API_KEY</code> in your .env to use this tab.</div>
                </div>
              </Card>
            )}

            {!wfLoading && workflows?.data && (
              <>
                {workflows.data.length === 0 ? (
                  <Card>
                    <div className="empty-state">
                      <div className="empty-state-title">No workflows yet</div>
                      <div>Open n8n and create your first workflow.</div>
                      <Btn variant="primary" size="sm" icon="globe" style={{ marginTop: 14 }} onClick={() => window.open(status?.url || "http://localhost:5678", "_blank")}>
                        Open n8n
                      </Btn>
                    </div>
                  </Card>
                ) : (
                  <Card pad="none">
                    <div className="tbl-wrap">
                      <table className="tbl">
                        <thead>
                          <tr>
                            <th>Workflow</th>
                            <th>Trigger</th>
                            <th>Nodes</th>
                            <th>Status</th>
                            <th></th>
                          </tr>
                        </thead>
                        <tbody>
                          {workflows.data.map(wf => {
                            const triggerNode = (wf.nodes || []).find(n =>
                              n.type?.includes("trigger") || n.type?.includes("webhook")
                            );
                            return (
                              <tr key={wf.id}>
                                <td>
                                  <div className="strong text-13">{wf.name}</div>
                                  <div className="mono text-11 muted">id: {wf.id}</div>
                                </td>
                                <td>
                                  <span className="chip">
                                    <span className="mono text-11">{triggerNode?.type?.split(".").pop() || "—"}</span>
                                  </span>
                                </td>
                                <td><span className="muted text-12">{(wf.nodes || []).length} nodes</span></td>
                                <td>
                                  <Badge tone={wf.active ? "ok" : "default"} dot>
                                    {wf.active ? "active" : "inactive"}
                                  </Badge>
                                </td>
                                <td className="right">
                                  <div className="row gap-2" style={{ justifyContent: "flex-end" }}>
                                    <Toggle checked={!!wf.active} onChange={() => toggleWorkflow(wf)} />
                                    <Btn variant="ghost" size="sm" icon="globe"
                                      onClick={() => window.open(`${status?.url || "http://localhost:5678"}/workflow/${wf.id}`, "_blank")} />
                                  </div>
                                </td>
                              </tr>
                            );
                          })}
                        </tbody>
                      </table>
                    </div>
                  </Card>
                )}
              </>
            )}
          </div>
        )}

        {/* ── Executions ─────────────────────────────────────────── */}
        {tab === "executions" && (
          <div>
            <div style={{ display: "flex", justifyContent: "flex-end", marginBottom: 12 }}>
              <Btn variant="ghost" size="sm" icon="activity" onClick={refreshExecutions}>Refresh</Btn>
            </div>

            {exLoading && (
              <Card><div className="empty-state"><div className="muted">Loading executions…</div></div></Card>
            )}

            {!exLoading && executions === null && (
              <Card>
                <div className="empty-state">
                  <div className="empty-state-title">Could not load executions</div>
                  <div className="muted text-12">n8n may be offline or API key is missing.</div>
                </div>
              </Card>
            )}

            {!exLoading && executions?.data && (
              executions.data.length === 0 ? (
                <Card>
                  <div className="empty-state">
                    <div className="empty-state-title">No executions yet</div>
                    <div>Fire an event from the Test Fire tab to trigger a workflow.</div>
                  </div>
                </Card>
              ) : (
                <Card pad="none">
                  <div className="tbl-wrap">
                    <table className="tbl">
                      <thead>
                        <tr><th>ID</th><th>Workflow</th><th>Status</th><th>Duration</th><th>Started</th></tr>
                      </thead>
                      <tbody>
                        {executions.data.map(ex => {
                          const dur = ex.stoppedAt && ex.startedAt
                            ? Math.round((new Date(ex.stoppedAt) - new Date(ex.startedAt)) / 1000)
                            : null;
                          return (
                            <tr key={ex.id}>
                              <td><span className="mono text-11 muted">{ex.id}</span></td>
                              <td><span className="text-12">{ex.workflowData?.name || ex.workflowId || "—"}</span></td>
                              <td>
                                <Badge tone={ex.status === "success" ? "ok" : ex.status === "error" ? "err" : "default"} dot>
                                  {ex.status || "—"}
                                </Badge>
                              </td>
                              <td><span className="muted text-12">{dur != null ? `${dur}s` : "—"}</span></td>
                              <td><span className="muted text-12">{ex.startedAt ? fmtDate(ex.startedAt) : "—"}</span></td>
                            </tr>
                          );
                        })}
                      </tbody>
                    </table>
                  </div>
                </Card>
              )
            )}
          </div>
        )}

        {/* ── VoxPro → n8n Automations ───────────────────────────── */}
        {tab === "automations" && (
          <AutomationsGuide
            automations={automations}
            n8nAutomations={n8nAutomations}
            autoLoading={autoLoading}
            showNewAuto={showNewAuto}
            setShowNewAuto={setShowNewAuto}
            newAuto={newAuto}
            setNewAuto={setNewAuto}
            saveAutomation={saveAutomation}
            deleteAutomation={deleteAutomation}
            setTab={setTab}
            setFireEvent={setFireEvent}
            assistantList={assistantList}
          />
        )}

        {/* ── Test Fire ──────────────────────────────────────────── */}
        {tab === "fire" && (
          <div className="g-2">
            <Card title="Fire a VoxPro event" subtitle="Sends the event to all matching n8n automations in real time.">
              <Field label="Trigger event">
                <Select value={fireEvent} onChange={e => setFireEvent(e.target.value)}>
                  {N8N_EVENTS.map(ev => <option key={ev} value={ev}>{ev}</option>)}
                </Select>
              </Field>
              <Field label="Payload (JSON)">
                <Textarea
                  value={firePayload}
                  onChange={e => setFirePayload(e.target.value)}
                  style={{ fontFamily: "var(--f-mono)", fontSize: 11.5, minHeight: 200 }}
                />
              </Field>
              <div className="row gap-2 mt-2">
                <Btn variant="primary" icon="play" disabled={firing} onClick={fireEventNow}>
                  {firing ? "Firing…" : "Fire event"}
                </Btn>
                <Btn variant="ghost" size="sm" onClick={() => setFirePayload(JSON.stringify(DEFAULT_PAYLOAD, null, 2))}>
                  Reset payload
                </Btn>
              </div>
            </Card>

            <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
              <Card title={fireResult ? (fireResult.error ? "Error" : `Fired · ${fireResult.matched ?? 0} matched`) : "Result"}>
                {fireResult ? (
                  fireResult.error ? (
                    <div style={{ color: "var(--err)", fontSize: 13 }}>{fireResult.error}</div>
                  ) : (
                    <div>
                      {(fireResult.results || []).length === 0 ? (
                        <div className="empty-state">
                          <div className="empty-state-title">No automations matched</div>
                          <div>Create an n8n automation for <code className="mono text-11">{fireEvent}</code> in the Automations tab.</div>
                        </div>
                      ) : (
                        <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
                          {(fireResult.results || []).map((r, i) => (
                            <div key={i} style={{ display: "flex", alignItems: "center", gap: 10, padding: "7px 10px", background: "var(--c-surface)", borderRadius: 7, fontSize: 12 }}>
                              <StatusDot online={r.ok} />
                              <span className="strong">{r.name}</span>
                              <span className="muted">HTTP {r.status || "—"}</span>
                              {r.error && <span style={{ color: "var(--err)" }}>{r.error}</span>}
                            </div>
                          ))}
                        </div>
                      )}
                    </div>
                  )
                ) : (
                  <div className="empty-state"><div className="muted text-12">Fire an event to see the result.</div></div>
                )}
              </Card>

              <Card title="Registered n8n automations" subtitle={`${n8nAutomations.length} automation(s) active`}>
                {autoLoading ? (
                  <div className="muted text-12">Loading…</div>
                ) : n8nAutomations.length === 0 ? (
                  <div className="muted text-12">No n8n automations. Go to the Automations tab to add one.</div>
                ) : (
                  <div style={{ display: "flex", flexDirection: "column", gap: 5 }}>
                    {n8nAutomations.map(a => (
                      <div key={a.id} style={{ display: "flex", alignItems: "center", gap: 8, fontSize: 12 }}>
                        <StatusDot online={a.enabled !== false} />
                        <span className="strong">{a.name}</span>
                        <span className="chip"><span className="mono text-11">{a.triggerEvent}</span></span>
                      </div>
                    ))}
                  </div>
                )}
              </Card>
            </div>
          </div>
        )}
      </div>
    </div>
  );
};

Object.assign(window, { N8nView });
