const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accentColor": "oklch(0.55 0.18 255)",
  "density": 1,
  "borderRadius": 12,
  "darkMode": true
}/*EDITMODE-END*/;

/* ========== TOKENS ========== */
const t = {
  accent: "var(--ocd-tweak-accent-color, oklch(0.55 0.18 255))",
  accentHover: "oklch(0.48 0.18 255)",
  accentLight: "oklch(0.7 0.1 255 / 0.15)",
  bg: "#f6f5f1",
  surface: "#ffffff",
  text: "#1a1a2e",
  textMuted: "#787878",
  border: "#e2e0dc",
  hoverBg: "#f0efeb",
  error: "#dc2626",
  success: "#059669",
  radius: "var(--ocd-tweak-border-radius, 12px)",
  radiusSm: "8px",
  font: "'SF Pro Text', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif",
  mono: "'SF Mono', ui-monospace, 'Cascadia Code', 'Source Code Pro', monospace",
};

const tDark = {
  accent: "var(--ocd-tweak-accent-color, oklch(0.55 0.18 255))",
  accentHover: "oklch(0.62 0.16 255)",
  accentLight: "oklch(0.5 0.12 255 / 0.18)",
  bg: "#18181f",
  surface: "#222233",
  text: "#f0f0f5",
  textMuted: "#9e9eab",
  border: "#33334d",
  hoverBg: "#2d2d42",
  error: "#f87171",
  success: "#34d399",
  radius: "var(--ocd-tweak-border-radius, 12px)",
  radiusSm: "8px",
  font: "'SF Pro Text', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif",
  mono: "'SF Mono', ui-monospace, 'Cascadia Code', 'Source Code Pro', monospace",
};

/* ========== LANGUAGES ========== */
const LANGUAGES = [
  { code: "ja", name: "日本語", native: "日本語" },
  { code: "en", name: "英語", native: "English" },
  { code: "zh", name: "中国語", native: "中文" },
  { code: "ko", name: "韓国語", native: "한국어" },
  { code: "fr", name: "フランス語", native: "Français" },
  { code: "de", name: "ドイツ語", native: "Deutsch" },
  { code: "es", name: "スペイン語", native: "Español" },
  { code: "it", name: "イタリア語", native: "Italiano" },
  { code: "pt", name: "ポルトガル語", native: "Português" },
  { code: "ru", name: "ロシア語", native: "Русский" },
  { code: "ar", name: "アラビア語", native: "العربية" },
  { code: "hi", name: "ヒンディー語", native: "हिन्दी" },
];

const API_URL = "/api/translate";

const SYSTEM_PROMPT = `Translate the following text into {target_lang}. Note that you should only output the translated result without any additional explanation:

{source_text}`;

/* ========== ICONS ========== */
const SwapIcon = () => (
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <path d="M7 16V4m0 0L3 8m4-4l4 4M17 8v12m0 0l4-4m-4 4l-4-4" />
  </svg>
);

const CopyIcon = () => (
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
    <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
  </svg>
);

const CheckIcon = () => (
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <polyline points="20 6 9 17 4 12" />
  </svg>
);

const MoonIcon = () => (
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
  </svg>
);

const SunIcon = () => (
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <circle cx="12" cy="12" r="5" />
    <line x1="12" y1="1" x2="12" y2="3" />
    <line x1="12" y1="21" x2="12" y2="23" />
    <line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
    <line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
    <line x1="1" y1="12" x2="3" y2="12" />
    <line x1="21" y1="12" x2="23" y2="12" />
    <line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
    <line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
  </svg>
);

const GlobeIcon = () => (
  <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="var(--ocd-tweak-accent-color, oklch(0.55 0.18 255))" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <circle cx="12" cy="12" r="10" />
    <line x1="2" y1="12" x2="22" y2="12" />
    <path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
  </svg>
);

const ChevronIcon = ({ open }) => (
  <svg
    width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
    strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"
    style={{ transition: "transform 0.15s", transform: open ? "rotate(180deg)" : "rotate(0deg)" }}
  >
    <polyline points="6 9 12 15 18 9" />
  </svg>
);

/* ========== LANGUAGE SELECT ========== */
function LangSelect({ value, onChange, label, exclude, tokens }) {
  const tok = tokens || t;
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);

  React.useEffect(() => {
    const handler = e => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", handler);
    return () => document.removeEventListener("mousedown", handler);
  }, []);

  const selected = LANGUAGES.find(l => l.code === value);

  return (
    <div ref={ref} style={{ position: "relative", flex: 1, minWidth: 0 }}>
      <button
        aria-label={label}
        aria-expanded={open}
        onClick={() => setOpen(prev => !prev)}
        style={{
          display: "flex",
          alignItems: "center",
          justifyContent: "space-between",
          gap: "6px",
          width: "100%",
          padding: "10px 12px",
          border: `1px solid ${tok.border}`,
          borderRadius: tok.radiusSm,
          background: tok.surface,
          color: tok.text,
          fontSize: "0.9rem",
          fontWeight: 500,
          fontFamily: tok.font,
          cursor: "pointer",
          transition: "background 0.15s, border-color 0.15s",
          outline: "none",
        }}
        onFocus={e => e.target.style.boxShadow = `0 0 0 2px ${tok.accentLight}`}
        onBlur={e => e.target.style.boxShadow = "none"}
        onMouseEnter={e => e.currentTarget.style.borderColor = tok.accent}
        onMouseLeave={e => { if (!open) e.currentTarget.style.borderColor = tok.border; }}
      >
        <span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
          {selected ? `${selected.native} (${selected.name})` : "選択"}
        </span>
        <ChevronIcon open={open} />
      </button>

      {open && (
        <div style={{
          position: "absolute",
          top: "calc(100% + 4px)",
          left: 0,
          right: 0,
          maxHeight: "280px",
          overflowY: "auto",
          background: tok.surface,
          border: `1px solid ${tok.border}`,
          borderRadius: tok.radiusSm,
          boxShadow: "0 8px 30px rgba(0,0,0,0.18)",
          zIndex: 50,
          animation: "fadeSlideIn 0.15s ease-out",
        }}>
          {LANGUAGES.filter(l => l.code !== exclude).map(lang => (
            <button
              key={lang.code}
              onClick={() => { onChange(lang.code); setOpen(false); }}
              aria-selected={lang.code === value}
              style={{
                display: "block",
                width: "100%",
                padding: "10px 12px",
                border: "none",
                background: lang.code === value ? tok.accentLight : "transparent",
                color: tok.text,
                fontSize: "0.9rem",
                textAlign: "left",
                cursor: "pointer",
                fontFamily: tok.font,
                transition: "background 0.1s",
              }}
              onMouseEnter={e => e.target.style.background = lang.code === value ? tok.accentLight : tok.hoverBg}
              onMouseLeave={e => e.target.style.background = lang.code === value ? tok.accentLight : "transparent"}
            >
              <span style={{ fontWeight: lang.code === value ? 600 : 400 }}>
                {lang.native}
              </span>
              {" "}
              <span style={{ fontSize: "0.8rem", opacity: 0.6 }}>
                {lang.name}
              </span>
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

/* ========== TOAST ========== */
function Toast({ message, kind, onDismiss, tokens }) {
  const tok = tokens || t;

  React.useEffect(() => {
    const timer = setTimeout(onDismiss, 2500);
    return () => clearTimeout(timer);
  }, [onDismiss]);

  const bg = kind === "error" ? tok.error : kind === "success" ? tok.success : "#333";
  return (
    <div
      role="alert"
      style={{
        position: "fixed",
        bottom: "24px",
        left: "50%",
        transform: "translateX(-50%)",
        background: bg,
        color: "#fff",
        padding: "12px 20px",
        borderRadius: tok.radiusSm,
        fontSize: "0.875rem",
        fontWeight: 500,
        fontFamily: tok.font,
        boxShadow: "0 4px 20px rgba(0,0,0,0.25)",
        zIndex: 100,
        animation: "fadeSlideUp 0.25s ease-out",
        maxWidth: "90vw",
        textAlign: "center",
        whiteSpace: "pre-line",
      }}
    >
      {message}
    </div>
  );
}

/* ========== MAIN APP ========== */
function App() {
  const [sourceLang, setSourceLang] = React.useState("ja");
  const [targetLang, setTargetLang] = React.useState("en");
  const [sourceText, setSourceText] = React.useState("");
  const [translatedText, setTranslatedText] = React.useState("");
  const [loading, setLoading] = React.useState(false);
  const [copied, setCopied] = React.useState(false);
  const [toast, setToast] = React.useState(null);
  const [testing, setTesting] = React.useState(false);
  const [testResult, setTestResult] = React.useState(null);
  const [darkMode, setDarkMode] = React.useState(TWEAK_DEFAULTS.darkMode !== false);
  const textareaRef = React.useRef(null);

  const tok = darkMode ? tDark : t;

  const showToast = React.useCallback((msg, kind) => {
    setToast({ message: msg, kind });
  }, []);

  const dismissToast = React.useCallback(() => {
    setToast(null);
  }, []);

  function toggleDarkMode() {
    setDarkMode(prev => !prev);
  }

  async function testConnection() {
    setTesting(true);
    setTestResult(null);

    try {
      const ctrl = new AbortController();
      const timeout = setTimeout(() => ctrl.abort(), 20000);

      const res = await fetch("/api/test", {
        method: "POST",
        signal: ctrl.signal,
      });

      clearTimeout(timeout);

      // レスポンスが空やHTMLの場合に備えてテキストで取得 → JSON パース
      const text = await res.text();
      if (!text) {
        setTestResult({
          ok: false,
          message: "サーバーから空の応答が返されました。Functions が正しくデプロイされているか確認してください。",
        });
      } else {
        try {
          const data = JSON.parse(text);
          setTestResult(data);
        } catch {
          setTestResult({
            ok: false,
            message: `サーバーから無効な応答がありました（JSONではありません）。\n\n先頭200文字:\n${text.slice(0, 200)}`,
          });
        }
      }
    } catch (err) {
      if (err.name === "AbortError") {
        setTestResult({
          ok: false,
          message: "接続診断がタイムアウトしました。",
        });
      } else {
        setTestResult({
          ok: false,
          message: `診断に失敗しました: ${err.message}`,
        });
      }
    } finally {
      setTesting(false);
    }
  }

  function swapLanguages() {
    if (loading) return;
    setSourceLang(targetLang);
    setTargetLang(sourceLang);
    setSourceText(translatedText);
    setTranslatedText(sourceText);
  }

  async function handleTranslate() {
    if (!sourceText.trim()) return;
    setLoading(true);
    setTranslatedText("");

    const targetLangName = LANGUAGES.find(l => l.code === targetLang)?.name || targetLang;
    const userPrompt = SYSTEM_PROMPT
      .replace("{target_lang}", targetLangName)
      .replace("{source_text}", sourceText);

    try {
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), 30000);

      const res = await fetch(API_URL, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          messages: [
            { role: "user", content: userPrompt }
          ],
          temperature: 0.3,
          max_tokens: 2048,
        }),
        signal: controller.signal,
      });

      clearTimeout(timeoutId);

      if (!res.ok) {
        const errText = await res.text().catch(() => "");
        throw new Error(`API error ${res.status}: ${errText || res.statusText}`);
      }

      const text = await res.text();
      if (!text) throw new Error("空のレスポンスを受信しました");

      let data;
      try {
        data = JSON.parse(text);
      } catch {
        throw new Error("サーバーから無効なJSONが返されました");
      }

      const result = data?.choices?.[0]?.message?.content?.trim();
      if (!result) throw new Error("翻訳結果が空でした");
      setTranslatedText(result);
    } catch (err) {
      if (err.name === "AbortError") {
        showToast("接続がタイムアウトしました。サーバーの状態を確認してください", "error");
      } else if (err.message === "Failed to fetch" || err.message.includes("Failed to fetch") || err.message.includes("NetworkError")) {
        showToast(
          "APIサーバーに接続できません。CORS設定を確認してください",
          "error"
        );
      } else {
        showToast(`翻訳エラー: ${err.message}`, "error");
      }
    } finally {
      setLoading(false);
    }
  }

  async function handleCopy() {
    if (!translatedText) return;
    try {
      await navigator.clipboard.writeText(translatedText);
      setCopied(true);
      showToast("翻訳結果をコピーしました", "success");
      setTimeout(() => setCopied(false), 2000);
    } catch {
      showToast("コピーに失敗しました", "error");
    }
  }

  function handleKeyDown(e) {
    if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
      e.preventDefault();
      handleTranslate();
    }
  }

  const canTranslate = sourceText.trim().length > 0 && !loading;

  return (
    <div style={{
      minHeight: "100vh",
      background: tok.bg,
      fontFamily: tok.font,
      color: tok.text,
      transition: "background 0.3s, color 0.3s",
    }}>
      {/* Header */}
      <header style={{
        borderBottom: `1px solid ${tok.border}`,
        background: tok.surface,
        padding: "0 16px",
        transition: "background 0.3s, border-color 0.3s",
      }}>
        <div style={{
          maxWidth: "880px",
          margin: "0 auto",
          display: "flex",
          alignItems: "center",
          gap: "12px",
          height: "60px",
        }}>
          <GlobeIcon />
          <span style={{ fontSize: "1.1rem", fontWeight: 600, letterSpacing: "-0.01em" }}>
            Manjiro Private
          </span>
          <div style={{ marginLeft: "auto", display: "flex", alignItems: "center", gap: "10px" }}>
            <span style={{
              fontSize: "0.75rem",
              color: tok.textMuted,
            }}>
              AI 翻訳
            </span>
            <button
              aria-label={darkMode ? "ライトモードに切り替え" : "ダークモードに切り替え"}
              onClick={toggleDarkMode}
              style={{
                display: "flex",
                alignItems: "center",
                justifyContent: "center",
                width: "34px",
                height: "34px",
                border: `1px solid ${tok.border}`,
                borderRadius: tok.radiusSm,
                background: tok.surface,
                color: tok.textMuted,
                cursor: "pointer",
                transition: "all 0.2s",
                outline: "none",
              }}
              onMouseEnter={e => { e.currentTarget.style.borderColor = tok.accent; e.currentTarget.style.color = tok.accent; }}
              onMouseLeave={e => { e.currentTarget.style.borderColor = tok.border; e.currentTarget.style.color = tok.textMuted; }}
              onFocus={e => e.target.style.boxShadow = `0 0 0 2px ${tok.accentLight}`}
              onBlur={e => e.target.style.boxShadow = "none"}
            >
              {darkMode ? <MoonIcon /> : <SunIcon />}
            </button>
          </div>
        </div>
      </header>

      {/* Main */}
      <main style={{
        maxWidth: "880px",
        margin: "0 auto",
        padding: "28px 16px 60px",
      }}>
        {/* Language Row */}
        <div style={{
          display: "flex",
          alignItems: "center",
          gap: "8px",
          marginBottom: "12px",
        }}>
          <LangSelect tokens={tok} value={sourceLang} onChange={setSourceLang} label="翻訳元言語" exclude={targetLang} />
          <button
            aria-label="言語を入れ替え"
            onClick={swapLanguages}
            disabled={loading}
            style={{
              flexShrink: 0,
              width: "40px",
              height: "40px",
              display: "flex",
              alignItems: "center",
              justifyContent: "center",
              border: `1px solid ${tok.border}`,
              borderRadius: "50%",
              background: tok.surface,
              cursor: loading ? "not-allowed" : "pointer",
              color: tok.textMuted,
              transition: "all 0.15s",
              opacity: loading ? 0.5 : 1,
            }}
            onMouseEnter={e => { if (!loading) { e.currentTarget.style.borderColor = tok.accent; e.currentTarget.style.color = tok.accent; } }}
            onMouseLeave={e => { e.currentTarget.style.borderColor = tok.border; e.currentTarget.style.color = tok.textMuted; }}
          >
            <SwapIcon />
          </button>
          <LangSelect tokens={tok} value={targetLang} onChange={setTargetLang} label="翻訳先言語" exclude={sourceLang} />
        </div>

        {/* Text Panels */}
        <div style={{
          display: "grid",
          gridTemplateColumns: "1fr 1fr",
          gap: "12px",
        }}>
          {/* Source Panel */}
          <div style={{
            background: tok.surface,
            border: `1px solid ${tok.border}`,
            borderRadius: tok.radius,
            overflow: "hidden",
          }}>
            <div style={{
              padding: "10px 14px",
              borderBottom: `1px solid ${tok.border}`,
              fontSize: "0.8rem",
              color: tok.textMuted,
              fontWeight: 500,
              display: "flex",
              justifyContent: "space-between",
              alignItems: "center",
            }}>
              <span>{LANGUAGES.find(l => l.code === sourceLang)?.name || "ソース"}</span>
              <span style={{ fontSize: "0.75rem" }}>{sourceText.length} 文字</span>
            </div>
            <textarea
              ref={textareaRef}
              value={sourceText}
              onChange={e => setSourceText(e.target.value)}
              onKeyDown={handleKeyDown}
              placeholder="翻訳するテキストを入力..."
              aria-label="翻訳元テキスト"
              style={{
                width: "100%",
                minHeight: "240px",
                border: "none",
                padding: "14px",
                fontSize: "1rem",
                lineHeight: "1.7",
                resize: "vertical",
                fontFamily: tok.font,
                color: tok.text,
                background: "transparent",
                outline: "none",
                boxSizing: "border-box",
              }}
            />
          </div>

          {/* Target Panel */}
          <div style={{
            background: tok.surface,
            border: `1px solid ${tok.border}`,
            borderRadius: tok.radius,
            overflow: "hidden",
          }}>
            <div style={{
              padding: "10px 14px",
              borderBottom: `1px solid ${tok.border}`,
              fontSize: "0.8rem",
              color: tok.textMuted,
              fontWeight: 500,
              display: "flex",
              justifyContent: "space-between",
              alignItems: "center",
            }}>
              <span>{LANGUAGES.find(l => l.code === targetLang)?.name || "ターゲット"}</span>
              {translatedText && (
                <button
                  onClick={handleCopy}
                  style={{
                    display: "flex",
                    alignItems: "center",
                    gap: "4px",
                    border: "none",
                    background: "transparent",
                    color: copied ? tok.success : tok.textMuted,
                    cursor: "pointer",
                    fontSize: "0.75rem",
                    fontWeight: 500,
                    fontFamily: tok.font,
                    padding: "2px 6px",
                    borderRadius: "4px",
                    transition: "all 0.15s",
                  }}
                >
                  {copied ? <CheckIcon /> : <CopyIcon />}
                  {copied ? "コピー済み" : "コピー"}
                </button>
              )}
            </div>
            <div
              role="status"
              aria-live="polite"
              style={{
                minHeight: "240px",
                padding: "14px",
                fontSize: "1rem",
                lineHeight: "1.7",
                color: translatedText ? tok.text : tok.textMuted,
                whiteSpace: "pre-wrap",
                wordBreak: "break-word",
              }}
            >
              {loading ? (
                <div style={{ display: "flex", flexDirection: "column", gap: "10px" }}>
                  {[94, 72, 88].map((w, i) => (
                    <div key={i} style={{
                      height: "16px",
                      width: `${w}%`,
                      borderRadius: "4px",
                      background: tok.border,
                      animation: "pulse 1.4s ease-in-out infinite",
                      animationDelay: `${i * 0.15}s`,
                    }} />
                  ))}
                </div>
              ) : translatedText || "翻訳結果がここに表示されます"}
            </div>
          </div>
        </div>

        {/* Translate Button */}
        <div style={{ marginTop: "16px", display: "flex", justifyContent: "center" }}>
          <button
            onClick={handleTranslate}
            disabled={!canTranslate}
            style={{
              display: "flex",
              alignItems: "center",
              justifyContent: "center",
              gap: "8px",
              padding: "14px 48px",
              background: canTranslate ? tok.accent : tok.border,
              color: canTranslate ? "#fff" : tok.textMuted,
              border: "none",
              borderRadius: tok.radiusSm,
              fontSize: "1rem",
              fontWeight: 600,
              fontFamily: tok.font,
              cursor: canTranslate ? "pointer" : "not-allowed",
              transition: "all 0.2s",
              boxShadow: canTranslate ? "0 2px 8px rgba(0,0,0,0.15)" : "none",
              outline: "none",
              maxWidth: "360px",
              width: "100%",
            }}
            onFocus={e => { if (canTranslate) e.target.style.boxShadow = `0 0 0 3px ${tok.accentLight}, 0 2px 8px rgba(0,0,0,0.08)`; }}
            onBlur={e => { e.target.style.boxShadow = canTranslate ? "0 2px 8px rgba(0,0,0,0.15)" : "none"; }}
            onMouseEnter={e => {
              if (canTranslate) {
                e.currentTarget.style.background = tok.accentHover;
                e.currentTarget.style.boxShadow = "0 6px 16px rgba(0,0,0,0.2)";
                e.currentTarget.style.transform = "translateY(-1px)";
              }
            }}
            onMouseLeave={e => {
              if (canTranslate) {
                e.currentTarget.style.background = tok.accent;
                e.currentTarget.style.boxShadow = "0 2px 8px rgba(0,0,0,0.15)";
                e.currentTarget.style.transform = "translateY(0)";
              }
            }}
          >
            {loading ? (
              <>
                <div style={{
                  width: "18px",
                  height: "18px",
                  border: "2px solid rgba(255,255,255,0.3)",
                  borderTopColor: "#fff",
                  borderRadius: "50%",
                  animation: "spin 0.6s linear infinite",
                }} />
                翻訳中...
              </>
            ) : "翻訳する（⌘+Enter）"}
          </button>
        </div>

        {/* Connection Test */}
        <div style={{ marginTop: "16px", display: "flex", flexDirection: "column", alignItems: "center", gap: "10px" }}>
          <button
            onClick={testConnection}
            disabled={testing}
            style={{
              padding: "8px 20px",
              background: "transparent",
              color: testing ? tok.textMuted : tok.textMuted,
              border: `1px solid ${tok.border}`,
              borderRadius: tok.radiusSm,
              fontSize: "0.8rem",
              fontWeight: 500,
              fontFamily: tok.font,
              cursor: testing ? "not-allowed" : "pointer",
              transition: "all 0.15s",
              opacity: testing ? 0.6 : 1,
            }}
            onMouseEnter={e => { if (!testing) { e.currentTarget.style.borderColor = tok.text; e.currentTarget.style.color = tok.text; } }}
            onMouseLeave={e => { e.currentTarget.style.borderColor = tok.border; e.currentTarget.style.color = tok.textMuted; }}
          >
            {testing ? (
              <>
                <span style={{
                  display: "inline-block",
                  width: "12px",
                  height: "12px",
                  border: "2px solid " + tok.border,
                  borderTopColor: tok.textMuted,
                  borderRadius: "50%",
                  animation: "spin 0.6s linear infinite",
                  marginRight: "6px",
                  verticalAlign: "middle",
                }} />
                接続診断中...
              </>
            ) : "API接続をテスト"}
          </button>

          {testResult && (
            <div style={{
              maxWidth: "600px",
              width: "100%",
              padding: "12px 16px",
              borderRadius: tok.radiusSm,
              background: testResult.ok
                ? (darkMode ? "#0a3320" : "#ecfdf5")
                : (darkMode ? "#3b1015" : "#fef2f2"),
              border: `1px solid ${testResult.ok ? tok.success + "30" : tok.error + "30"}`,
              fontSize: "0.8rem",
              lineHeight: "1.65",
              color: tok.text,
              whiteSpace: "pre-wrap",
              fontFamily: tok.mono,
            }}>
              {testResult.message}
            </div>
          )}
        </div>
      </main>

      {/* Toast */}
      {toast && (
        <Toast
          tokens={tok}
          message={toast.message}
          kind={toast.kind}
          onDismiss={dismissToast}
        />
      )}

      {/* Keyframes */}
      <style>{`
        @keyframes fadeSlideIn {
          from { opacity: 0; transform: translateY(-6px); }
          to { opacity: 1; transform: translateY(0); }
        }
        @keyframes fadeSlideUp {
          from { opacity: 0; transform: translateX(-50%) translateY(12px); }
          to { opacity: 1; transform: translateX(-50%) translateY(0); }
        }
        @keyframes pulse {
          0%, 100% { opacity: 0.4; }
          50% { opacity: 0.8; }
        }
        @keyframes spin {
          to { transform: rotate(360deg); }
        }
        @media (max-width: 640px) {
          div[style*="grid-template-columns: 1fr 1fr"] {
            grid-template-columns: 1fr !important;
          }
        }
      `}</style>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
