'use client';

import { CodeBlock } from '@/components/code-block';
import { Input, Label, Textarea } from '@/components/input';
import { InstallSnippets } from '@/components/install-snippets';
import { StreamingLogs } from '@/components/streaming-logs';
import { Button } from '@/components/ui/button';
import { apiFetch, apiSseStream } from '@/lib/api';
import { Loader2, RotateCcw, X } from 'lucide-react';
import Link from 'next/link';
import { useRouter, useSearchParams } from 'next/navigation';
import { Suspense, useEffect, useState } from 'react';

const EXAMPLE_PROMPTS = [
  {
    label: 'Echo / demo (no external API)',
    text: 'Build a simple echo server with two tools: echo (string in, same string back) and now (returns current UTC timestamp). No external API needed.',
  },
  {
    label: 'Notion search',
    text: 'Search and read pages from our Notion workspace via the Notion API. Tools: search_pages(query), get_page_content(page_id). Auth: NOTION_API_KEY.',
  },
  {
    label: 'Postgres reader',
    text: 'Read-only Postgres reader for the users and orders tables at db.example.com. One tool per table: list_users(limit), list_orders(limit, customer_id?). Auth: DATABASE_URL.',
  },
  {
    label: 'Wrap our REST API',
    text: 'Wrap our internal HTTP API at api.acme.com — endpoints /search?q= and /lookup?id=. Tools: search(query), lookup(id). Auth: ACME_API_TOKEN.',
  },
  {
    label: 'Stripe charges (read-only)',
    text: 'Stripe charges and customers, read-only. Tools: list_recent_charges(limit), get_customer(customer_id). Auth: STRIPE_SECRET_KEY.',
  },
];

type Step = 'prompt' | 'analyzing' | 'confirm' | 'building' | 'done';

interface PreviewTool {
  name: string;
  description: string;
  inputSchema: Record<string, unknown>;
}

type Plan = 'hobby' | 'pro' | 'team' | 'enterprise';

interface PreviewResponse {
  previewId: string;
  source: 'claude' | 'glm' | 'mock';
  plan?: Plan;
  modelDisplayName?: string;
  modelBadge?: 'open-tier' | 'claude-haiku' | 'claude-sonnet' | 'claude-opus';
  upgradeHint?: boolean;
  spec: {
    name: string;
    description?: string;
    tools: PreviewTool[];
    requiredSecrets: string[];
    scopes: string[];
  };
}

const PREVIEW_MODEL_BY_PLAN: Record<Plan, { name: string; estimate: string }> = {
  hobby: { name: 'Open-tier AI', estimate: '30–60 seconds' },
  pro: { name: 'Claude Haiku 4.5', estimate: '10–20 seconds' },
  team: { name: 'Claude Sonnet 4.6', estimate: '15–40 seconds' },
  enterprise: { name: 'Claude Sonnet 4.6', estimate: '15–40 seconds' },
};

interface EditableTool {
  name: string;
  description: string;
  inputSchemaJson: string;
  schemaError: string | null;
}

interface EditableSpec {
  tools: EditableTool[];
  requiredSecrets: string[];
}

interface BuildResult {
  serverId: string;
  publicUrl: string | null;
}

function specToEditable(spec: PreviewResponse['spec']): EditableSpec {
  return {
    tools: spec.tools.map((t) => ({
      name: t.name,
      description: t.description,
      inputSchemaJson: JSON.stringify(t.inputSchema, null, 2),
      schemaError: null,
    })),
    requiredSecrets: [...spec.requiredSecrets],
  };
}

function NewServerPageInner() {
  const router = useRouter();
  const [step, setStep] = useState<Step>('prompt');
  const [elapsedSec, setElapsedSec] = useState(0);
  const [userPlan, setUserPlan] = useState<Plan | null>(null);

  const [prompt, setPrompt] = useState('');
  const [name, setName] = useState('');
  const [slug, setSlug] = useState('');

  const [preview, setPreview] = useState<PreviewResponse | null>(null);
  const [editable, setEditable] = useState<EditableSpec | null>(null);
  const [secretValues, setSecretValues] = useState<Record<string, string>>({});

  const [error, setError] = useState<string | null>(null);
  const [buildId, setBuildId] = useState<string | null>(null);
  const [serverId, setServerId] = useState<string | null>(null);
  const [result, setResult] = useState<BuildResult | null>(null);
  const [forkedTemplateId, setForkedTemplateId] = useState<string | null>(null);
  const [forkedTemplateTitle, setForkedTemplateTitle] = useState<string | null>(null);

  const searchParams = useSearchParams();
  const templateSlug = searchParams.get('template');

  const trySlug = (n: string) =>
    n
      .toLowerCase()
      .replace(/[^a-z0-9-]+/g, '-')
      .replace(/^-+|-+$/g, '')
      .slice(0, 32);

  // Fork-from-template flow: skip Step 1, jump straight to Step 2 with the template's spec
  useEffect(() => {
    if (!templateSlug || preview) return;
    let cancelled = false;
    setStep('analyzing');
    (async () => {
      try {
        const res = await apiFetch<{
          previewId: string;
          templateId: string;
          template: {
            slug: string;
            title: string;
            shortDescription: string;
            tools: PreviewTool[];
            requiredSecrets: Array<{
              key: string;
              description: string;
              howToGetUrl?: string;
            }>;
          };
        }>(`/v1/templates/${templateSlug}/fork`, { method: 'POST', body: '{}' });
        if (cancelled) return;

        // Auto-unique the slug against the user's existing servers so the
        // default fork doesn't 409 (e.g. forking the same template twice).
        let uniqueSlug = trySlug(res.template.title);
        try {
          const own = await apiFetch<{ servers: { slug: string }[] }>('/v1/servers');
          const taken = new Set(own.servers.map((s) => s.slug));
          if (taken.has(uniqueSlug)) {
            const base = uniqueSlug;
            let n = 2;
            while (taken.has(`${base}-${n}`)) n++;
            uniqueSlug = `${base}-${n}`;
          }
        } catch {
          // If the lookup fails we still proceed; the slug field is editable
          // and the build surfaces a clear slug_taken error.
        }
        if (cancelled) return;

        setName(res.template.title);
        setSlug(uniqueSlug);
        setPrompt(`Fork of "${res.template.title}" template.`);
        setForkedTemplateId(res.templateId);
        setForkedTemplateTitle(res.template.title);
        setPreview({
          previewId: res.previewId,
          source: 'mock',
          spec: {
            name: res.template.title,
            description: res.template.shortDescription,
            tools: res.template.tools,
            requiredSecrets: res.template.requiredSecrets.map((s) => s.key),
            scopes: [],
          },
        });
        setEditable(null);
        setStep('confirm');
      } catch (e) {
        if (cancelled) return;
        const detail = (e as { detail?: { error?: string } }).detail;
        setError(detail?.error ?? (e as Error).message);
        setStep('prompt');
      }
    })();
    return () => {
      cancelled = true;
    };
  }, [templateSlug, preview]);

  useEffect(() => {
    if (preview && !editable) {
      const e = specToEditable(preview.spec);
      setEditable(e);
      const initial: Record<string, string> = {};
      for (const key of e.requiredSecrets) initial[key] = '';
      setSecretValues(initial);
    }
  }, [preview, editable]);

  // Live elapsed counter for the analyze step — a value that ticks every
  // second is unambiguous proof the page is alive, even when CSS animation is
  // suppressed (e.g. the OS "reduce motion" setting).
  useEffect(() => {
    if (step !== 'analyzing') return;
    setElapsedSec(0);
    const startedAt = Date.now();
    const id = setInterval(() => {
      setElapsedSec(Math.floor((Date.now() - startedAt) / 1000));
    }, 1000);
    return () => clearInterval(id);
  }, [step]);

  // Plan determines which model the preview will use — we display its name
  // *before* the request so the user knows what they're waiting for.
  useEffect(() => {
    apiFetch<{ user: { plan?: Plan } }>('/v1/auth/me')
      .then((r) => setUserPlan(r.user.plan ?? 'hobby'))
      .catch(() => setUserPlan('hobby'));
  }, []);

  async function analyze() {
    setError(null);
    if (prompt.trim().length < 10) {
      setError('Prompt must be at least 10 characters.');
      return;
    }
    if (!name || !slug) {
      setError('Name and slug are required.');
      return;
    }
    setStep('analyzing');

    // Streaming preview: pipes Anthropic's token deltas back as SSE. Cloudflare's
    // ~100s edge cap doesn't bite because every chunk we receive resets the
    // idle timer; the only practical limit is the model's own runtime. If the
    // backend returns 409 streaming_unavailable (e.g. hobby/GLM tier), we fall
    // back to the sync endpoint so the wizard still works there.
    let finalResolved = false;
    let sseError: { error?: string; detail?: string } | null = null;
    let sseSpec: PreviewResponse | null = null;

    await apiSseStream(
      '/v1/servers/preview/stream',
      { prompt },
      {
        onEvent: (event, data) => {
          if (event === 'spec') {
            finalResolved = true;
            sseSpec = data as PreviewResponse;
          } else if (event === 'error') {
            finalResolved = true;
            sseError = data as { error?: string; detail?: string };
          }
          // 'text' deltas are ignored for now — the wizard already shows a
          // spinner. We could surface partial JSON later if useful.
        },
        onError: (err) => {
          sseError = { detail: err.message };
        },
      },
    );

    if (finalResolved && sseSpec) {
      setPreview(sseSpec);
      setEditable(null);
      setStep('confirm');
      return;
    }

    if (sseError && (sseError as { error?: string }).error === 'streaming_unavailable') {
      // GLM / mock tier — fall back to sync.
      try {
        const res = await apiFetch<PreviewResponse>('/v1/servers/preview', {
          method: 'POST',
          body: JSON.stringify({ prompt }),
        });
        setPreview(res);
        setEditable(null);
        setStep('confirm');
        return;
      } catch (e) {
        const detail = (e as { detail?: { error?: string; detail?: string } }).detail;
        setError(detail?.detail ?? detail?.error ?? (e as Error).message);
        setStep('prompt');
        return;
      }
    }

    setError(
      (sseError as { detail?: string } | null)?.detail ??
        (sseError as { error?: string } | null)?.error ??
        'Spec generation failed.',
    );
    setStep('prompt');
  }

  function updateTool(i: number, patch: Partial<EditableTool>) {
    setEditable((prev) => {
      if (!prev) return prev;
      const tools = [...prev.tools];
      const cur = tools[i];
      if (!cur) return prev;
      tools[i] = { ...cur, ...patch };
      return { ...prev, tools };
    });
  }

  function updateToolSchema(i: number, json: string) {
    let schemaError: string | null = null;
    try {
      const parsed = JSON.parse(json);
      if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
        schemaError = 'Schema must be a JSON object.';
      }
    } catch (e) {
      schemaError = `JSON parse error: ${(e as Error).message}`;
    }
    updateTool(i, { inputSchemaJson: json, schemaError });
  }

  function addSecret() {
    setEditable((prev) =>
      prev ? { ...prev, requiredSecrets: [...prev.requiredSecrets, ''] } : prev,
    );
  }

  function removeSecret(idx: number) {
    setEditable((prev) => {
      if (!prev) return prev;
      const removedKey = prev.requiredSecrets[idx];
      const next = prev.requiredSecrets.filter((_, i) => i !== idx);
      const sv = { ...secretValues };
      if (removedKey) delete sv[removedKey];
      setSecretValues(sv);
      return { ...prev, requiredSecrets: next };
    });
  }

  function updateSecretKey(idx: number, newKey: string) {
    const cleaned = newKey.toUpperCase().replace(/[^A-Z0-9_]/g, '');
    setEditable((prev) => {
      if (!prev) return prev;
      const prevKey = prev.requiredSecrets[idx];
      const next = [...prev.requiredSecrets];
      next[idx] = cleaned;
      const sv = { ...secretValues };
      if (prevKey && prevKey in sv) {
        sv[cleaned] = sv[prevKey] ?? '';
        if (prevKey !== cleaned) delete sv[prevKey];
      }
      setSecretValues(sv);
      return { ...prev, requiredSecrets: next };
    });
  }

  function resetEdits() {
    if (preview) setEditable(specToEditable(preview.spec));
  }

  async function build() {
    setError(null);
    if (!preview || !editable) return;

    // Validate every schema parses
    const badTool = editable.tools.find((t) => t.schemaError);
    if (badTool) {
      setError(`Fix schema for "${badTool.name}": ${badTool.schemaError}`);
      return;
    }

    // Validate secret keys: non-empty + UPPER_SNAKE_CASE
    for (const key of editable.requiredSecrets) {
      if (!key) {
        setError('Empty secret name. Remove the row or fill it in.');
        return;
      }
      if (!/^[A-Z][A-Z0-9_]*$/.test(key)) {
        setError(`Secret name "${key}" must be UPPER_SNAKE_CASE.`);
        return;
      }
    }

    // Validate filled secret values for present keys
    const filledSecrets: Record<string, string> = {};
    for (const k of editable.requiredSecrets) {
      const v = secretValues[k] ?? '';
      if (v.trim()) filledSecrets[k] = v;
    }
    const missing = editable.requiredSecrets.filter((k) => !filledSecrets[k]);
    if (missing.length > 0) {
      setError(`Fill values for: ${missing.join(', ')} — or remove from the list.`);
      return;
    }

    const specEdit = {
      tools: editable.tools.map((t) => ({
        name: t.name,
        description: t.description,
        inputSchema: JSON.parse(t.inputSchemaJson),
      })),
      requiredSecrets: editable.requiredSecrets,
    };

    try {
      const res = await apiFetch<{ server: { id: string }; build: { id: string } }>('/v1/servers', {
        method: 'POST',
        body: JSON.stringify({
          name,
          slug,
          prompt,
          secrets: filledSecrets,
          previewId: preview.previewId,
          // Don't send specEdit when forking — the template's spec + pre-rendered code
          // are already in the Redis cache. Edits would invalidate the impls.
          ...(forkedTemplateId ? { templateId: forkedTemplateId } : { specEdit }),
        }),
      });
      setBuildId(res.build.id);
      setServerId(res.server.id);
      setStep('building');
    } catch (e) {
      const detail = (e as { detail?: { error?: string; detail?: string } }).detail;
      const code = detail?.error;
      if (code === 'slug_taken') {
        setError(
          `The slug "${slug}" is already used by one of your servers — change the Slug field above.`,
        );
        return;
      }
      if (code === 'plan_limit_reached') {
        setError(`${detail?.detail ?? 'Plan limit reached.'} See /pricing to upgrade.`);
        return;
      }
      if (code === 'rate_limited') {
        setError(detail?.detail ?? 'Daily build limit reached — try again tomorrow or upgrade.');
        return;
      }
      setError(detail?.detail ?? code ?? (e as Error).message);
    }
  }

  const stepNumber = step === 'prompt' ? 1 : step === 'analyzing' || step === 'confirm' ? 2 : 3;
  const hasSchemaErrors = editable?.tools.some((t) => t.schemaError);
  const editsDirty =
    preview && editable
      ? JSON.stringify(specToEditable(preview.spec)) !== JSON.stringify(editable)
      : false;

  return (
    <div className="mx-auto max-w-3xl px-6 py-8">
      <div className="flex items-baseline justify-between">
        <h1 className="text-[22px] font-semibold tracking-tight">New MCP server</h1>
        <div className="mono text-[11px] tracking-wider text-[--color-fg-subtle]">
          STEP {stepNumber} / 3
        </div>
      </div>

      {step === 'prompt' && (
        <div className="mt-7 space-y-5">
          <div className="space-y-1.5">
            <Label htmlFor="prompt">Describe what your tool should do</Label>
            <Textarea
              id="prompt"
              rows={5}
              value={prompt}
              onChange={(e) => setPrompt(e.target.value)}
              placeholder="A sentence is enough. Mention which APIs you need, which scopes, what the AI client should be able to do."
            />
            <p className="text-[12px] leading-relaxed text-[--color-fg-subtle]">
              Next step we&apos;ll show you exactly which tools we&apos;ll expose and let you tweak
              the spec before we build.
            </p>
            <div className="flex flex-wrap gap-1.5 pt-1">
              {EXAMPLE_PROMPTS.map((p) => (
                <button
                  type="button"
                  key={p.label}
                  onClick={() => setPrompt(p.text)}
                  className="rounded-full border border-[--color-border] bg-[--color-bg-subtle] px-2.5 py-1 text-[11.5px] text-[--color-fg-muted] transition-colors hover:border-[--color-border-strong] hover:text-[--color-fg]"
                >
                  {p.label}
                </button>
              ))}
            </div>
          </div>

          <div className="grid gap-3 md:grid-cols-2">
            <div className="space-y-1.5">
              <Label htmlFor="name">Name</Label>
              <Input
                id="name"
                value={name}
                onChange={(e) => {
                  setName(e.target.value);
                  if (!slug || slug === trySlug(name)) setSlug(trySlug(e.target.value));
                }}
                placeholder="Notion Reader"
              />
            </div>
            <div className="space-y-1.5">
              <Label htmlFor="slug" hint="becomes subdomain / id">
                Slug
              </Label>
              <Input
                id="slug"
                value={slug}
                onChange={(e) => setSlug(trySlug(e.target.value))}
                placeholder="notion-reader"
              />
            </div>
          </div>

          {error && <p className="text-[12.5px] text-[--color-danger]">{error}</p>}

          <div className="flex justify-end gap-2">
            <Button variant="ghost" size="md" onClick={() => router.push('/servers')}>
              Cancel
            </Button>
            <Button variant="primary" size="md" onClick={analyze}>
              Analyze →
            </Button>
          </div>
        </div>
      )}

      {step === 'analyzing' && (
        <div className="mt-10 panel p-8 text-center">
          <Loader2 className="mx-auto animate-spin text-[--color-accent]" size={22} />
          <p className="mt-4 text-[13px]">Analyzing your prompt…</p>
          <p className="mt-1 text-[12px] text-[--color-fg-subtle]">
            {(userPlan ? PREVIEW_MODEL_BY_PLAN[userPlan] : PREVIEW_MODEL_BY_PLAN.hobby).name} is
            drafting the tool spec. Usually{' '}
            {(userPlan ? PREVIEW_MODEL_BY_PLAN[userPlan] : PREVIEW_MODEL_BY_PLAN.hobby).estimate}.
          </p>
          {userPlan === 'hobby' && (
            <p className="mt-2 text-[11px] text-[--color-fg-muted]">
              <Link href="/pricing" className="text-[--color-accent] hover:underline">
                Upgrade to Pro
              </Link>{' '}
              for ~3× faster analysis with Claude Haiku.
            </p>
          )}
          <p className="mono mt-3 text-[11px] tabular-nums text-[--color-fg-muted]">
            {elapsedSec}s elapsed
          </p>
        </div>
      )}

      {step === 'confirm' && preview && editable && (
        <div className="mt-7 space-y-6">
          {forkedTemplateTitle && (
            <div className="panel-subtle p-3 space-y-3">
              <div className="flex items-center justify-between">
                <div className="text-[12.5px]">
                  Forking <span className="mono font-semibold">{forkedTemplateTitle}</span> — name
                  your copy and fill in your own credentials. The template author never sees them.
                </div>
                <a
                  href={`/templates/${templateSlug}`}
                  target="_blank"
                  rel="noreferrer"
                  className="shrink-0 text-[11.5px] text-[--color-fg-muted] underline hover:text-[--color-fg]"
                >
                  Template ↗
                </a>
              </div>
              <div className="grid gap-3 md:grid-cols-2">
                <div className="space-y-1.5">
                  <Label htmlFor="fork-name">Name</Label>
                  <Input
                    id="fork-name"
                    value={name}
                    onChange={(e) => {
                      setName(e.target.value);
                      if (!slug || slug === trySlug(name)) setSlug(trySlug(e.target.value));
                    }}
                  />
                </div>
                <div className="space-y-1.5">
                  <Label htmlFor="fork-slug" hint="must be unique in your workspace">
                    Slug
                  </Label>
                  <Input
                    id="fork-slug"
                    value={slug}
                    onChange={(e) => setSlug(trySlug(e.target.value))}
                  />
                </div>
              </div>
            </div>
          )}
          <div className="panel p-4">
            <div className="flex items-baseline justify-between">
              <h2 className="text-[14px] font-semibold tracking-tight">
                Confirm what we&apos;ll build
              </h2>
              <div className="flex items-center gap-3">
                {editsDirty && (
                  <button
                    type="button"
                    onClick={resetEdits}
                    className="inline-flex items-center gap-1 text-[11px] text-[--color-fg-muted] transition-colors hover:text-[--color-fg]"
                  >
                    <RotateCcw size={11} /> reset edits
                  </button>
                )}
                <span className="mono text-[10.5px] text-[--color-fg-subtle]">
                  drafted with {preview.modelDisplayName ?? preview.source}
                </span>
              </div>
            </div>
            <p className="mt-1 text-[12.5px] text-[--color-fg-muted]">{preview.spec.description}</p>
            <p className="mt-3 text-[11.5px] leading-relaxed text-[--color-fg-subtle]">
              Edit tool names, descriptions or input schemas inline. Renaming parameters may require
              an <span className="mono">Iterate</span> after build to update the implementation —
              the existing impl references the original names.
            </p>
          </div>

          <div>
            <h3 className="text-[13px] font-semibold tracking-tight">
              Tools ({editable.tools.length})
            </h3>
            <div className="mt-2 space-y-3">
              {editable.tools.map((tool, i) => (
                <div key={i} className="panel p-3 space-y-2.5">
                  <div className="grid gap-2 md:grid-cols-[1fr_auto]">
                    <Input
                      value={tool.name}
                      onChange={(e) => updateTool(i, { name: e.target.value })}
                      placeholder="snake_case_tool_name"
                      className="mono"
                    />
                    <span className="mono self-center text-[10.5px] text-[--color-fg-subtle]">
                      {Object.keys(safeJsonObject(tool.inputSchemaJson)).length} param
                      {Object.keys(safeJsonObject(tool.inputSchemaJson)).length === 1 ? '' : 's'}
                    </span>
                  </div>
                  <Textarea
                    rows={2}
                    value={tool.description}
                    onChange={(e) => updateTool(i, { description: e.target.value })}
                    placeholder="What the AI client sees — one clear sentence."
                  />
                  <div className="space-y-1">
                    <Label hint="JSON object — keys are param names, values describe each param">
                      Input schema
                    </Label>
                    <Textarea
                      rows={Math.min(12, Math.max(3, tool.inputSchemaJson.split('\n').length))}
                      value={tool.inputSchemaJson}
                      onChange={(e) => updateToolSchema(i, e.target.value)}
                      className="mono text-[12px]"
                      spellCheck={false}
                    />
                    {tool.schemaError && (
                      <p className="text-[11.5px] text-[--color-danger]">{tool.schemaError}</p>
                    )}
                  </div>
                </div>
              ))}
            </div>
          </div>

          <div>
            <h3 className="text-[13px] font-semibold tracking-tight">Credentials we need</h3>
            <p className="mt-1 text-[12px] leading-relaxed text-[--color-fg-muted]">
              AES-256-GCM encrypted at rest, injected as env vars at runtime. Remove if your
              implementation doesn&apos;t actually use one.
            </p>
            <div className="mt-3 space-y-2">
              {editable.requiredSecrets.length === 0 && (
                <p className="text-[12.5px] text-[--color-fg-muted]">
                  No credentials. This server runs self-contained.
                </p>
              )}
              {editable.requiredSecrets.map((key, idx) => (
                <div key={idx} className="grid grid-cols-[180px_1fr_auto] items-start gap-2">
                  <Input
                    value={key}
                    onChange={(e) => updateSecretKey(idx, e.target.value)}
                    placeholder="MY_API_KEY"
                    className="mono"
                  />
                  <Input
                    type="password"
                    value={secretValues[key] ?? ''}
                    onChange={(e) => setSecretValues((s) => ({ ...s, [key]: e.target.value }))}
                    placeholder="paste value"
                  />
                  <button
                    type="button"
                    onClick={() => removeSecret(idx)}
                    aria-label="Remove"
                    className="inline-flex h-8 items-center justify-center rounded-md border border-[--color-border] px-2 text-[--color-fg-muted] transition-colors hover:text-[--color-fg]"
                  >
                    <X size={13} />
                  </button>
                </div>
              ))}
              <Button variant="ghost" size="sm" onClick={addSecret}>
                + Add credential
              </Button>
            </div>
          </div>

          {preview.spec.scopes.length > 0 && (
            <div>
              <h3 className="text-[13px] font-semibold tracking-tight">OAuth scopes</h3>
              <div className="mt-2 flex flex-wrap gap-1.5">
                {preview.spec.scopes.map((s) => (
                  <span
                    key={s}
                    className="mono rounded-full border border-[--color-border] bg-[--color-bg-subtle] px-2 py-0.5 text-[11px] text-[--color-fg-muted]"
                  >
                    {s}
                  </span>
                ))}
              </div>
            </div>
          )}

          {error && <p className="text-[12.5px] text-[--color-danger]">{error}</p>}

          <div className="flex justify-end gap-2">
            <Button variant="ghost" size="md" onClick={() => setStep('prompt')}>
              ← Back
            </Button>
            <Button variant="primary" size="md" onClick={build} disabled={Boolean(hasSchemaErrors)}>
              Build server →
            </Button>
          </div>
        </div>
      )}

      {step === 'building' && buildId && (
        <div className="mt-7 space-y-4">
          <p className="text-[13px] text-[--color-fg-muted]">
            Building your server. Logs stream live over WebSocket from the generator.
          </p>
          <StreamingLogs
            buildId={buildId}
            onDone={(status, publicUrl) => {
              if (status === 'success') {
                setResult({ serverId: serverId!, publicUrl });
                setStep('done');
              } else {
                setError(`Build ${status}`);
              }
            }}
          />
          {error && <p className="text-[12.5px] text-[--color-danger]">{error}</p>}
        </div>
      )}

      {step === 'done' && result && (
        <div className="mt-7 space-y-6">
          <div className="panel p-4">
            <div className="flex items-baseline justify-between">
              <h2 className="text-[14px] font-semibold tracking-tight">Your server is live</h2>
              <span className="mono text-[11px] text-emerald-300">200 OK</span>
            </div>
            <p className="mt-1 text-[12.5px] text-[--color-fg-muted]">
              OAuth-protected Streamable HTTP endpoint:
            </p>
            <div className="mt-3">
              <CodeBlock code={`${result.publicUrl}/mcp`} label="endpoint" />
            </div>
          </div>

          {result.publicUrl && (
            <div>
              <h2 className="text-[14px] font-semibold tracking-tight">Install</h2>
              <p className="mt-1 text-[12.5px] text-[--color-fg-muted]">
                Drop this into your client. OAuth handshake runs automatically on first use.
              </p>
              <div className="mt-3">
                <InstallSnippets input={{ name, slug, publicUrl: result.publicUrl }} />
              </div>
            </div>
          )}

          {!forkedTemplateId && (
            <SharePanel
              serverId={result.serverId}
              defaultTitle={name}
              defaultShortDescription={preview?.spec.description ?? ''}
              secretKeys={editable?.requiredSecrets ?? []}
            />
          )}

          <div className="flex justify-end gap-2">
            <Button
              variant="secondary"
              size="md"
              onClick={() => router.push(`/servers/${result.serverId}`)}
            >
              Open server →
            </Button>
          </div>
        </div>
      )}
    </div>
  );
}

// useSearchParams() forces client-side rendering — Next requires a Suspense
// boundary around it, or `next build` bails out of static generation.
export default function NewServerPage() {
  return (
    <Suspense
      fallback={
        <div className="mx-auto max-w-3xl px-6 py-8">
          <h1 className="text-[22px] font-semibold tracking-tight">New MCP server</h1>
          <div className="panel mt-10 p-8 text-center">
            <Loader2 className="mx-auto animate-spin text-[--color-fg-muted]" size={20} />
            <p className="mt-4 text-[13px] text-[--color-fg-muted]">Loading…</p>
          </div>
        </div>
      }
    >
      <NewServerPageInner />
    </Suspense>
  );
}

const SHARE_CATEGORIES = [
  'productivity',
  'developer-tools',
  'data',
  'communication',
  'finance',
  'crm',
  'analytics',
  'devops',
  'demo',
  'other',
];

function SharePanel({
  serverId,
  defaultTitle,
  defaultShortDescription,
  secretKeys,
}: {
  serverId: string;
  defaultTitle: string;
  defaultShortDescription: string;
  secretKeys: string[];
}) {
  const [share, setShare] = useState(true);
  const [category, setCategory] = useState('other');
  const [shortDescription, setShortDescription] = useState(defaultShortDescription.slice(0, 280));
  const [hints, setHints] = useState<Record<string, string>>(() =>
    Object.fromEntries(secretKeys.map((k) => [k, ''])),
  );
  const [state, setState] = useState<'idle' | 'submitting' | 'done' | 'error'>('idle');
  const [error, setError] = useState<string | null>(null);
  const [publishedSlug, setPublishedSlug] = useState<string | null>(null);

  async function publish() {
    setError(null);
    if (shortDescription.trim().length < 10) {
      setError('Add a short description (at least 10 characters).');
      return;
    }
    setState('submitting');
    try {
      const res = await apiFetch<{ template: { slug: string } }>('/v1/templates', {
        method: 'POST',
        body: JSON.stringify({
          serverId,
          title: defaultTitle,
          category,
          shortDescription: shortDescription.trim(),
          secretHints: secretKeys.map((k) => ({
            key: k,
            description: hints[k]?.trim() || `Credential required for this server (${k}).`,
          })),
        }),
      });
      setPublishedSlug(res.template.slug);
      setState('done');
    } catch (e) {
      const detail = (e as { detail?: { error?: string; detail?: string } }).detail;
      setError(detail?.detail ?? detail?.error ?? (e as Error).message);
      setState('error');
    }
  }

  if (state === 'done' && publishedSlug) {
    return (
      <div className="panel p-4">
        <div className="flex items-baseline justify-between">
          <h2 className="text-[14px] font-semibold tracking-tight">Shared to marketplace</h2>
          <span className="mono text-[11px] text-emerald-300">public</span>
        </div>
        <p className="mt-1 text-[12.5px] text-[--color-fg-muted]">
          Others can now fork it. You can unshare anytime from the server&apos;s Publish tab.
        </p>
        <a
          href={`/templates/${publishedSlug}`}
          target="_blank"
          rel="noreferrer"
          className="mt-3 inline-flex h-8 items-center rounded-md bg-[--color-accent] px-3 text-[12.5px] font-medium text-white transition-colors hover:bg-[#5557e8]"
        >
          View in marketplace →
        </a>
      </div>
    );
  }

  return (
    <div className="panel p-4">
      <label className="flex cursor-pointer items-start gap-2.5">
        <input
          type="checkbox"
          checked={share}
          onChange={(e) => setShare(e.target.checked)}
          className="mt-0.5 size-3.5 accent-[--color-accent]"
        />
        <div>
          <div className="text-[13px] font-medium">
            Share as template in the marketplace{' '}
            <span className="text-[--color-fg-subtle]">(recommended)</span>
          </div>
          <p className="mt-1 text-[12px] leading-relaxed text-[--color-fg-muted]">
            Your secrets stay private — they are never copied into a template. But your{' '}
            <span className="text-[--color-fg]">generated code becomes publicly viewable</span> so
            others can audit it before forking. Unshare anytime.
          </p>
        </div>
      </label>

      {share && (
        <div className="mt-4 space-y-3 border-t border-[--color-border] pt-4">
          <div className="grid gap-3 md:grid-cols-[1fr_200px]">
            <div className="space-y-1.5">
              <Label hint={`${shortDescription.length}/280`}>Short description</Label>
              <Input
                value={shortDescription}
                onChange={(e) => setShortDescription(e.target.value)}
                placeholder="What does this server do, in one line?"
                maxLength={280}
              />
            </div>
            <div className="space-y-1.5">
              <Label>Category</Label>
              <select
                value={category}
                onChange={(e) => setCategory(e.target.value)}
                className="h-8 w-full rounded-md border border-[--color-border] bg-[--color-bg-subtle] px-2.5 text-[13px] focus:border-[--color-accent] focus:outline-none"
              >
                {SHARE_CATEGORIES.map((c) => (
                  <option key={c} value={c}>
                    {c}
                  </option>
                ))}
              </select>
            </div>
          </div>

          {secretKeys.length > 0 && (
            <div className="space-y-1.5">
              <Label hint="optional — helps forkers know what to paste">Credential hints</Label>
              {secretKeys.map((k) => (
                <div key={k} className="grid grid-cols-[180px_1fr] gap-2">
                  <div className="mono flex h-8 items-center rounded-md border border-[--color-border] bg-[--color-bg-subtle] px-2.5 text-[12px] text-[--color-fg-muted]">
                    {k}
                  </div>
                  <Input
                    value={hints[k] ?? ''}
                    onChange={(e) => setHints((h) => ({ ...h, [k]: e.target.value }))}
                    placeholder={`What is ${k}? Where does a forker get one?`}
                  />
                </div>
              ))}
            </div>
          )}

          {error && <p className="text-[12.5px] text-[--color-danger]">{error}</p>}

          <div className="flex items-center justify-between">
            <p className="text-[11px] text-[--color-fg-subtle]">
              Published code is re-scanned for banned patterns and hardcoded secrets.
            </p>
            <Button variant="primary" size="md" onClick={publish} disabled={state === 'submitting'}>
              {state === 'submitting' ? 'Publishing…' : 'Publish to marketplace'}
            </Button>
          </div>
        </div>
      )}
    </div>
  );
}

function safeJsonObject(s: string): Record<string, unknown> {
  try {
    const parsed = JSON.parse(s);
    if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
      return parsed as Record<string, unknown>;
    }
  } catch {}
  return {};
}
