/**
 * Overlay modal for editing Superpowers settings. Sits on top of the kanban
 * without unmounting it — matches the LogModal / NewIssueModal pattern:
 *  - Backdrop click is a no-op (only ESC closes, and only when `canCancel`).
 *  - Two-column layout: vertical group rail on the left, fields on the right.
 *
 * Replaces the old full-screen `SetupForm.tsx`.
 */

import type { ReactElement } from 'react'
import { useEffect, useState } from 'react'
import { X } from 'lucide-react'
import type { ClaudeProfile } from '../hooks/useIssues'

type GroupKey = 'auth' | 'network' | 'youtrack' | 'prompts' | 'hooks'

interface SubmitValues {
  host: string
  token: string
  webhookPort: number
  brainstormPrompt: string
  brainstormContinuePrompt: string
  implementPlanPrompt: string
  autoReview: boolean
  reviewPrompt: string
  devBranch: string
  autoBuildBranch: string
  worktreeDirectory: string
  worktreeOpenWith: 'vscode' | 'pycharm'
  pycharmCommand: string
  worktreePostCreateScript: string
  worktreePreRemoveScript: string
  implTabPreCreateScript: string
  implTabPostCloseScript: string
  conflictResolutionProfilePath: string
  commitProfilePath: string
  codexModel: string
  codexReasoningEffort: string
  profilesDirectory: string
  youtrackBaseUrl: string
  youtrackProjectShortName: string
  youtrackCloseCommand: string
  youtrackToken: string
}

/** Project option for the YouTrack project dropdown. */
export interface YouTrackProjectOption {
  id: string
  name: string
  shortName: string
}

export interface YouTrackProjectsState {
  status: 'idle' | 'loading' | 'ready' | 'error'
  projects: YouTrackProjectOption[]
  error?: string
}

export interface SettingsModalProps {
  open: boolean
  host: string
  errorMessage?: string
  canCancel?: boolean
  initialTokenSaved: boolean
  initialWebhookPort: number
  initialBrainstormPrompt: string
  initialBrainstormContinuePrompt: string
  initialImplementPlanPrompt: string
  initialAutoReview: boolean
  initialReviewPrompt: string
  initialDevBranch: string
  initialAutoBuildBranch: string
  initialWorktreeDirectory: string
  initialWorktreeOpenWith: 'vscode' | 'pycharm'
  initialPycharmCommand: string
  initialWorktreePostCreateScript: string
  initialWorktreePreRemoveScript: string
  initialImplTabPreCreateScript: string
  initialImplTabPostCloseScript: string
  initialConflictResolutionProfilePath: string
  initialCommitProfilePath: string
  initialCodexModel: string
  initialCodexReasoningEffort: string
  initialProfilesDirectory: string
  initialYoutrackBaseUrl: string
  initialYoutrackProjectShortName: string
  initialYoutrackCloseCommand: string
  initialYoutrackTokenSaved: boolean
  profiles: ClaudeProfile[]
  youtrackProjects: YouTrackProjectsState
  onListYouTrackProjects: (baseUrl: string, token: string) => void
  onSubmit: (values: SubmitValues) => void
  onCancel?: () => void
}

const inputClass = 'w-full rounded border border-[var(--vscode-input-border,transparent)] bg-[var(--vscode-input-background)] px-2 py-1 text-xs text-[var(--vscode-input-foreground)] outline-none focus:border-[var(--vscode-focusBorder)]'

interface FieldProps {
  label: string
  hint?: React.ReactNode
  error?: string
  children: React.ReactNode
}

function Field({ label, hint, error, children }: FieldProps) {
  return (
    <label className="mb-3 block text-xs">
      <span className="mb-1 block opacity-80">{label}</span>
      {children}
      {hint && (
        <span className="mt-1 block text-[10px] opacity-60">{hint}</span>
      )}
      {error && (
        <span className="mt-1 block text-[10px] text-state-red">{error}</span>
      )}
    </label>
  )
}

const GROUPS: Array<{ key: GroupKey, label: string }> = [
  { key: 'auth', label: '认证' },
  { key: 'network', label: '网络' },
  { key: 'youtrack', label: 'YouTrack' },
  { key: 'prompts', label: '提示词' },
  { key: 'hooks', label: '钩子' },
]

type ErrorKey = 'host' | 'token' | 'webhookPort'

/** Map a field name to the group it lives in, so we can auto-switch to the
 * first invalid field's group on save. */
const FIELD_TO_GROUP: Record<ErrorKey, GroupKey> = {
  host: 'auth',
  token: 'auth',
  webhookPort: 'network',
}

export function SettingsModal({
  open,
  host: initialHost,
  errorMessage,
  canCancel,
  initialTokenSaved,
  initialWebhookPort,
  initialBrainstormPrompt,
  initialBrainstormContinuePrompt,
  initialImplementPlanPrompt,
  initialAutoReview,
  initialReviewPrompt,
  initialDevBranch,
  initialAutoBuildBranch,
  initialWorktreeDirectory,
  initialWorktreeOpenWith,
  initialPycharmCommand,
  initialWorktreePostCreateScript,
  initialWorktreePreRemoveScript,
  initialImplTabPreCreateScript,
  initialImplTabPostCloseScript,
  initialConflictResolutionProfilePath,
  initialCommitProfilePath,
  initialCodexModel,
  initialCodexReasoningEffort,
  initialProfilesDirectory,
  initialYoutrackBaseUrl,
  initialYoutrackProjectShortName,
  initialYoutrackCloseCommand,
  initialYoutrackTokenSaved,
  profiles,
  youtrackProjects,
  onListYouTrackProjects,
  onSubmit,
  onCancel,
}: SettingsModalProps): ReactElement | null {
  const [activeGroup, setActiveGroup] = useState<GroupKey>('auth')
  const [host, setHost] = useState(initialHost)
  const [token, setToken] = useState('')
  const [webhookPort, setWebhookPort] = useState<string>(String(initialWebhookPort))
  const [brainstormPrompt, setBrainstormPrompt] = useState(initialBrainstormPrompt)
  const [brainstormContinuePrompt, setBrainstormContinuePrompt] = useState(initialBrainstormContinuePrompt)
  const [implementPlanPrompt, setImplementPlanPrompt] = useState(initialImplementPlanPrompt)
  const [autoReview, setAutoReview] = useState(initialAutoReview)
  const [reviewPrompt, setReviewPrompt] = useState(initialReviewPrompt)
  const [devBranch, setDevBranch] = useState(initialDevBranch)
  const [autoBuildBranch, setAutoBuildBranch] = useState(initialAutoBuildBranch)
  const [worktreeDirectory, setWorktreeDirectory] = useState(initialWorktreeDirectory)
  const [worktreeOpenWith, setWorktreeOpenWith] = useState(initialWorktreeOpenWith)
  const [pycharmCommand, setPycharmCommand] = useState(initialPycharmCommand)
  const [worktreePostCreateScript, setWorktreePostCreateScript] = useState(initialWorktreePostCreateScript)
  const [worktreePreRemoveScript, setWorktreePreRemoveScript] = useState(initialWorktreePreRemoveScript)
  const [implTabPreCreateScript, setImplTabPreCreateScript] = useState(initialImplTabPreCreateScript)
  const [implTabPostCloseScript, setImplTabPostCloseScript] = useState(initialImplTabPostCloseScript)
  const [conflictResolutionProfilePath, setConflictResolutionProfilePath] = useState(initialConflictResolutionProfilePath)
  const [commitProfilePath, setCommitProfilePath] = useState(initialCommitProfilePath)
  const [codexModel, setCodexModel] = useState(initialCodexModel)
  const [codexReasoningEffort, setCodexReasoningEffort] = useState(initialCodexReasoningEffort)
  const [profilesDirectory, setProfilesDirectory] = useState(initialProfilesDirectory)
  const [youtrackBaseUrl, setYoutrackBaseUrl] = useState(initialYoutrackBaseUrl)
  const [youtrackProjectShortName, setYoutrackProjectShortName] = useState(initialYoutrackProjectShortName)
  const [youtrackCloseCommand, setYoutrackCloseCommand] = useState(initialYoutrackCloseCommand)
  const [youtrackToken, setYoutrackToken] = useState('')
  const [errors, setErrors] = useState<Partial<Record<ErrorKey, string>>>({})

  // Reset local state whenever the modal opens with fresh server values.
  // We intentionally don't depend on initialBrainstormPrompt/initialImplementPlanPrompt
  // to mid-edit; this effect just primes form state on each (re)open.
  useEffect(() => {
    if (!open)
      return
    setActiveGroup('auth')
    setHost(initialHost)
    setToken('')
    setWebhookPort(String(initialWebhookPort))
    setBrainstormPrompt(initialBrainstormPrompt)
    setBrainstormContinuePrompt(initialBrainstormContinuePrompt)
    setImplementPlanPrompt(initialImplementPlanPrompt)
    setAutoReview(initialAutoReview)
    setReviewPrompt(initialReviewPrompt)
    setDevBranch(initialDevBranch)
    setAutoBuildBranch(initialAutoBuildBranch)
    setWorktreeDirectory(initialWorktreeDirectory)
    setWorktreeOpenWith(initialWorktreeOpenWith)
    setPycharmCommand(initialPycharmCommand)
    setWorktreePostCreateScript(initialWorktreePostCreateScript)
    setWorktreePreRemoveScript(initialWorktreePreRemoveScript)
    setImplTabPreCreateScript(initialImplTabPreCreateScript)
    setImplTabPostCloseScript(initialImplTabPostCloseScript)
    setConflictResolutionProfilePath(initialConflictResolutionProfilePath)
    setCommitProfilePath(initialCommitProfilePath)
    setCodexModel(initialCodexModel)
    setCodexReasoningEffort(initialCodexReasoningEffort)
    setProfilesDirectory(initialProfilesDirectory)
    setYoutrackBaseUrl(initialYoutrackBaseUrl)
    setYoutrackProjectShortName(initialYoutrackProjectShortName)
    setYoutrackCloseCommand(initialYoutrackCloseCommand)
    setYoutrackToken('')
    setErrors({})
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [open])

  // ESC closes when allowed.
  useEffect(() => {
    if (!open)
      return
    function onKey(e: KeyboardEvent): void {
      if (e.key === 'Escape' && canCancel && onCancel) {
        e.preventDefault()
        onCancel()
      }
    }
    document.addEventListener('keydown', onKey)
    return () => document.removeEventListener('keydown', onKey)
  }, [open, canCancel, onCancel])

  if (!open)
    return null

  const trimmedHost = host.trim()
  const tokenPageUrl = trimmedHost
    ? `https://${trimmedHost}/user/settings/applications`
    : ''

  function handleSubmit(event: React.FormEvent): void {
    event.preventDefault()
    const nextErrors: Partial<Record<ErrorKey, string>> = {}
    const trimmedToken = token.trim()
    if (!trimmedHost)
      nextErrors.host = 'Host 不能为空'
    // When a token is already saved server-side, leaving the field blank
    // means "keep the existing token" — don't flag it as an error. Only
    // require a new token when nothing is saved yet.
    if (!trimmedToken && !initialTokenSaved)
      nextErrors.token = 'Token 不能为空'
    const portNum = Number.parseInt(webhookPort, 10)
    if (!Number.isInteger(portNum) || portNum < 1 || portNum > 65535)
      nextErrors.webhookPort = '端口必须是 1–65535 的整数'

    if (Object.keys(nextErrors).length > 0) {
      setErrors(nextErrors)
      // Auto-jump to the group containing the first invalid field, so the
      // inline error is visible without the user hunting for it.
      const order: ErrorKey[] = ['host', 'token', 'webhookPort']
      for (const key of order) {
        if (nextErrors[key]) {
          setActiveGroup(FIELD_TO_GROUP[key])
          break
        }
      }
      return
    }
    setErrors({})
    onSubmit({
      host: trimmedHost,
      // Pass raw token (may be empty string when keeping existing). The
      // extension side detects empty + existing-saved as "preserve".
      token: trimmedToken,
      webhookPort: portNum,
      brainstormPrompt,
      brainstormContinuePrompt,
      implementPlanPrompt,
      autoReview,
      reviewPrompt,
      devBranch: devBranch.trim(),
      autoBuildBranch: autoBuildBranch.trim(),
      // worktree 目录模板：trim 后留空 = 用默认模板（getSettings 读时回退）
      worktreeDirectory: worktreeDirectory.trim(),
      worktreeOpenWith,
      // pycharm 启动命令：trim 后留空 = 用默认 `pycharm`（getSettings 读时回退）
      pycharmCommand: pycharmCommand.trim(),
      // 钩子路径保持原样 trim（前后空格无意义；空串 = 用默认 .spx/*.sh）
      worktreePostCreateScript: worktreePostCreateScript.trim(),
      worktreePreRemoveScript: worktreePreRemoveScript.trim(),
      implTabPreCreateScript: implTabPreCreateScript.trim(),
      implTabPostCloseScript: implTabPostCloseScript.trim(),
      // 冲突解决 profile：空串 = 用 DEFAULT_PROFILE_PATH
      conflictResolutionProfilePath: conflictResolutionProfilePath.trim(),
      // 提交代码 profile：空串 = 未配置 → 提交严格失败
      commitProfilePath: commitProfilePath.trim(),
      // codex 模型 / 思考级别：trim 后留空 = 用 codex config.toml 默认
      codexModel: codexModel.trim(),
      codexReasoningEffort: codexReasoningEffort.trim(),
      // profiles 目录：trim 后留空 = 自动探测
      profilesDirectory: profilesDirectory.trim(),
      youtrackBaseUrl: youtrackBaseUrl.trim(),
      youtrackProjectShortName: youtrackProjectShortName.trim(),
      youtrackCloseCommand: youtrackCloseCommand.trim(),
      // Raw (may be empty = keep existing), mirroring the gitea token rule.
      youtrackToken: youtrackToken.trim(),
    })
  }

  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60">
      <form
        onSubmit={handleSubmit}
        role="dialog"
        aria-modal="true"
        className="relative flex h-[min(600px,80vh)] w-[min(900px,90vw)] flex-col rounded-md border border-[var(--vscode-panel-border)] bg-[var(--vscode-editor-background)] shadow-xl"
      >
        {/* Header */}
        <div className="flex items-center gap-2 border-b border-[var(--vscode-panel-border)] px-4 py-2">
          <h2 className="text-sm font-medium">设置</h2>
          {canCancel && onCancel && (
            <button
              type="button"
              onClick={onCancel}
              aria-label="关闭"
              className="ml-auto grid size-6 place-items-center rounded opacity-60 hover:opacity-100"
            >
              <X className="size-4" />
            </button>
          )}
        </div>

        {/* Body */}
        <div className="flex min-h-0 flex-1 flex-row">
          {/* Left rail */}
          <div className="flex w-40 shrink-0 flex-col border-r border-[var(--vscode-panel-border)] py-2">
            {GROUPS.map((g) => {
              const active = g.key === activeGroup
              return (
                <button
                  key={g.key}
                  type="button"
                  onClick={() => setActiveGroup(g.key)}
                  className={[
                    'px-3 py-1.5 text-left text-xs',
                    active
                      ? 'bg-white/10 text-[var(--vscode-foreground)]'
                      : 'opacity-80 hover:bg-white/5',
                  ].join(' ')}
                >
                  {g.label}
                </button>
              )
            })}
          </div>

          {/* Right pane */}
          <div className="min-h-0 flex-1 overflow-y-auto p-4">
            {errorMessage && (
              <div className="mb-4 rounded border border-state-red/60 bg-state-red/10 px-3 py-2 text-xs text-state-red">
                {errorMessage}
              </div>
            )}

            {activeGroup === 'auth' && (
              <>
                <Field label="Gitea Host" error={errors.host}>
                  <input
                    type="text"
                    value={host}
                    onChange={e => setHost(e.target.value)}
                    placeholder="gitea.example.com"
                    className={inputClass}
                  />
                </Field>

                <Field
                  label="Personal Access Token"
                  error={errors.token}
                  hint={(
                    <>
                      在
                      {' '}
                      {tokenPageUrl
                        ? (
                            <a
                              href={tokenPageUrl}
                              target="_blank"
                              rel="noreferrer"
                              className="text-[var(--vscode-textLink-foreground)] underline"
                            >
                              {tokenPageUrl}
                            </a>
                          )
                        : <span className="opacity-60">填写 Host 后这里会显示生成 token 的链接</span>}
                      {' '}
                      生成一个 token
                      {initialTokenSaved && (
                        <>
                          <br />
                          已保存，留空则保留
                        </>
                      )}
                    </>
                  )}
                >
                  <input
                    type="password"
                    value={token}
                    onChange={e => setToken(e.target.value)}
                    placeholder={initialTokenSaved ? '••••••••' : '例如 1a2b3c4d…'}
                    className={inputClass}
                  />
                </Field>

                <Field
                  label="开发分支"
                  hint={(
                    <>
                      日常推送的分支，留空默认
                      {' '}
                      <code>main</code>
                      。同步按钮把这条分支的最新提交快进到下面的构建分支。
                    </>
                  )}
                >
                  <input
                    type="text"
                    value={devBranch}
                    onChange={e => setDevBranch(e.target.value)}
                    placeholder="main"
                    className={inputClass}
                  />
                </Field>

                <Field
                  label="自动化构建分支"
                  hint={(
                    <>
                      gitea webhook → Jenkins 监听的分支。留空表示与开发分支相同，同步按钮会一直禁用。
                    </>
                  )}
                >
                  <input
                    type="text"
                    value={autoBuildBranch}
                    onChange={e => setAutoBuildBranch(e.target.value)}
                    placeholder="留空 = 与开发分支相同"
                    className={inputClass}
                  />
                </Field>

                <Field
                  label="冲突解决配置文件"
                  hint={(
                    <>
                      拖到「完成」时若 PR 与 main/dev 冲突，自动开的冲突解决会话使用此 profile；留空用默认 official。
                    </>
                  )}
                >
                  <select
                    value={conflictResolutionProfilePath}
                    onChange={e => setConflictResolutionProfilePath(e.target.value)}
                    className={inputClass}
                  >
                    <option value="">（默认）</option>
                    {profiles.map(p => (
                      <option key={p.path} value={p.path}>{p.name}</option>
                    ))}
                    {conflictResolutionProfilePath
                      && !profiles.some(p => p.path === conflictResolutionProfilePath) && (
                      <option value={conflictResolutionProfilePath}>
                        {`自定义：${conflictResolutionProfilePath}`}
                      </option>
                    )}
                  </select>
                </Field>

                <Field
                  label="提交代码 profile"
                  hint={(
                    <>
                      顶部工具栏「提交代码」使用此 profile。留空则提交失败，不会回退默认。
                    </>
                  )}
                >
                  <select
                    value={commitProfilePath}
                    onChange={e => setCommitProfilePath(e.target.value)}
                    className={inputClass}
                  >
                    <option value="">（未配置）</option>
                    {profiles.map(p => (
                      <option key={p.path} value={p.path}>{p.name}</option>
                    ))}
                    {commitProfilePath
                      && !profiles.some(p => p.path === commitProfilePath) && (
                      <option value={commitProfilePath}>
                        {`自定义：${commitProfilePath}`}
                      </option>
                    )}
                  </select>
                </Field>

                <Field
                  label="codex 模型"
                  hint={(
                    <>
                      审查会话使用的 codex 模型（
                      <code>-c model=</code>
                      ）。留空则用 codex
                      {' '}
                      <code>config.toml</code>
                      {' '}
                      默认。
                    </>
                  )}
                >
                  <input
                    type="text"
                    value={codexModel}
                    onChange={e => setCodexModel(e.target.value)}
                    placeholder="留空用 codex 默认（如 gpt-5.6-sol）"
                    className={inputClass}
                  />
                </Field>

                <Field
                  label="codex 思考级别"
                  hint={(
                    <>
                      审查会话的
                      {' '}
                      <code>model_reasoning_effort</code>
                      ；留空则用 codex 默认。
                    </>
                  )}
                >
                  <input
                    type="text"
                    value={codexReasoningEffort}
                    onChange={e => setCodexReasoningEffort(e.target.value)}
                    placeholder="留空用 codex 默认（minimal/low/medium/high/xhigh）"
                    className={inputClass}
                  />
                </Field>
              </>
            )}

            {activeGroup === 'network' && (
              <>
                <Field
                  label="本地端口"
                  error={errors.webhookPort}
                  hint={(
                    <>
                      扩展 HTTP server 绑定的端口。在 gitea 仓库 Webhooks 配置里把目标 URL 指向
                      {' '}
                      <code>{'http://<你的本机/公网域名>:<此端口>/webhook'}</code>
                      ，事件需勾选：<b>工单</b>（issue opened/edited）、<b>合并请求</b>（PR opened/synchronize/closed）、<b>工单评论</b>（codex 审查回流）。
                    </>
                  )}
                >
                  <input
                    type="number"
                    min={1}
                    max={65535}
                    value={webhookPort}
                    onChange={e => setWebhookPort(e.target.value)}
                    className={inputClass}
                  />
                </Field>

                <Field
                  label="开启自动审查"
                  hint="PR 创建后自动用 codex exec review 跑审查并把结果灌到实施会话终端"
                >
                  <label className="inline-flex cursor-pointer items-center gap-2 text-xs">
                    <input
                      type="checkbox"
                      checked={autoReview}
                      onChange={e => setAutoReview(e.target.checked)}
                    />
                    <span className="opacity-80">启用</span>
                  </label>
                </Field>

                <Field
                  label="Claude profiles 目录"
                  hint={(
                    <>
                      claude --settings 用的 .json 所在目录。留空自动探测（优先
                      {' '}
                      <code>~/Sources/cruldra-profile/claude-config/profiles</code>
                      ）。支持
                      {' '}
                      <code>~</code>
                      。
                    </>
                  )}
                >
                  <input
                    type="text"
                    value={profilesDirectory}
                    onChange={e => setProfilesDirectory(e.target.value)}
                    placeholder="~/Sources/cruldra-profile/claude-config/profiles"
                    className={inputClass}
                  />
                </Field>
              </>
            )}

            {activeGroup === 'youtrack' && (
              <>
                <Field
                  label="YouTrack Base URL"
                  hint="例如 https://ziwuxian.youtrack.cloud。留空 = 关闭 YouTrack 源。"
                >
                  <input
                    type="text"
                    value={youtrackBaseUrl}
                    onChange={e => setYoutrackBaseUrl(e.target.value)}
                    placeholder="https://xxx.youtrack.cloud"
                    className={inputClass}
                  />
                </Field>

                <Field
                  label="Permanent Token"
                  hint={(
                    <>
                      在 YouTrack「个人资料 → Account Security → New Token」生成。
                      {initialYoutrackTokenSaved && (
                        <>
                          <br />
                          已保存，留空则保留
                        </>
                      )}
                    </>
                  )}
                >
                  <input
                    type="password"
                    value={youtrackToken}
                    onChange={e => setYoutrackToken(e.target.value)}
                    placeholder={initialYoutrackTokenSaved ? '••••••••' : 'perm-xxxx…'}
                    className={inputClass}
                  />
                </Field>

                <Field
                  label="关联项目"
                  hint="填 shortName（如 LXF），或点「加载项目」从下拉选择。"
                >
                  <div className="flex gap-2">
                    <input
                      type="text"
                      value={youtrackProjectShortName}
                      onChange={e => setYoutrackProjectShortName(e.target.value)}
                      placeholder="LXF"
                      className={inputClass}
                    />
                    <button
                      type="button"
                      onClick={() => onListYouTrackProjects(youtrackBaseUrl.trim(), youtrackToken.trim())}
                      className="shrink-0 rounded border border-[var(--vscode-button-border,transparent)] bg-[var(--vscode-button-secondaryBackground)] px-2 py-1 text-xs text-[var(--vscode-button-secondaryForeground)] hover:opacity-90"
                    >
                      {youtrackProjects.status === 'loading' ? '加载中…' : '加载项目'}
                    </button>
                  </div>
                  {youtrackProjects.status === 'ready' && youtrackProjects.projects.length > 0 && (
                    <select
                      value={youtrackProjectShortName}
                      onChange={e => setYoutrackProjectShortName(e.target.value)}
                      className={`${inputClass} mt-2`}
                    >
                      <option value="">— 选择项目 —</option>
                      {youtrackProjects.projects.map(p => (
                        <option key={p.id} value={p.shortName}>
                          {p.shortName}
                          {' · '}
                          {p.name}
                        </option>
                      ))}
                    </select>
                  )}
                  {youtrackProjects.status === 'error' && (
                    <span className="mt-1 block text-[10px] text-state-red">
                      加载失败：
                      {youtrackProjects.error}
                    </span>
                  )}
                </Field>

                <Field
                  label="关闭命令（可选）"
                  hint="卡片拖到「完成」时对 YouTrack 工单执行的命令，如 State Fixed。留空 = 自动探测项目里第一个「已解决」状态。"
                >
                  <input
                    type="text"
                    value={youtrackCloseCommand}
                    onChange={e => setYoutrackCloseCommand(e.target.value)}
                    placeholder="留空 = 自动探测"
                    className={inputClass}
                  />
                </Field>
              </>
            )}

            {activeGroup === 'prompts' && (
              <>
                <Field
                  label="头脑风暴提示词"
                  hint={(
                    <>
                      可用占位符：
                      <code>{'{userRequest}'}</code>
                      、
                      <code>{'{nonce}'}</code>
                      。同时承担：建工单初始指令 + 会话内 spec/plan 路径写回 issue body 的持续约定。
                    </>
                  )}
                >
                  <textarea
                    rows={12}
                    value={brainstormPrompt}
                    onChange={e => setBrainstormPrompt(e.target.value)}
                    className={`${inputClass} resize-y font-mono`}
                    placeholder="头脑风暴 + 建工单 + 持续约定提示词"
                  />
                </Field>

                <Field
                  label="头脑风暴续聊提示词"
                  hint={(
                    <>
                      可用占位符
                      {' '}
                      <code>{'{issueNumber}'}</code>
                      ；用于对已有工单继续讨论 / 写 spec/plan 的会话初始指令。
                    </>
                  )}
                >
                  <textarea
                    rows={8}
                    value={brainstormContinuePrompt}
                    onChange={e => setBrainstormContinuePrompt(e.target.value)}
                    className={`${inputClass} resize-y font-mono`}
                  />
                </Field>

                <Field
                  label="实施提示词"
                  hint={(
                    <>
                      可用占位符：
                      <code>{'{planFile}'}</code>
                      、
                      <code>{'{issueNumber}'}</code>
                    </>
                  )}
                >
                  <textarea
                    rows={8}
                    value={implementPlanPrompt}
                    onChange={e => setImplementPlanPrompt(e.target.value)}
                    className={`${inputClass} resize-y font-mono`}
                  />
                </Field>

                <Field
                  label="审查提示词"
                  hint={<>可用占位符：<code>{'{prNumber}'}</code></>}
                >
                  <textarea
                    rows={4}
                    value={reviewPrompt}
                    onChange={e => setReviewPrompt(e.target.value)}
                    className={`${inputClass} resize-y font-mono`}
                  />
                </Field>
              </>
            )}

            {activeGroup === 'hooks' && (
              <>
                <Field
                  label="工作树目录"
                  hint={(
                    <>
                      实施 Plan 时创建 worktree 的目录模板。占位符
                      {' '}
                      <code>$project_root</code>
                      （工作区绝对路径）、
                      <code>$project_name</code>
                      （工作区目录名）、
                      <code>$feature_name</code>
                      （spec/plan 的 slug）；开头
                      {' '}
                      <code>~</code>
                      {' '}
                      展开为主目录。留空 = 默认
                      {' '}
                      <code>~/Sources/worktree/$project_name/$feature_name</code>
                      。
                    </>
                  )}
                >
                  <input
                    type="text"
                    value={worktreeDirectory}
                    onChange={e => setWorktreeDirectory(e.target.value)}
                    placeholder="~/Sources/worktree/$project_name/$feature_name"
                    className={inputClass}
                  />
                </Field>

                <Field
                  label="工作树打开方式"
                  hint={(
                    <>
                      点击工单详情里的工作树时用哪个编辑器打开。PyCharm 需
                      {' '}
                      <code>pycharm</code>
                      {' '}
                      命令在 PATH 中（JetBrains Toolbox 里启用命令行启动器）。
                    </>
                  )}
                >
                  <select
                    value={worktreeOpenWith}
                    onChange={e => setWorktreeOpenWith(e.target.value as 'vscode' | 'pycharm')}
                    className={inputClass}
                  >
                    <option value="vscode">VS Code</option>
                    <option value="pycharm">PyCharm</option>
                  </select>
                </Field>

                {worktreeOpenWith === 'pycharm' && (
                  <Field
                    label="PyCharm 启动命令"
                    hint={(
                      <>
                        可填绝对路径或
                        {' '}
                        <code>~</code>
                        {' '}
                        开头路径（如
                        {' '}
                        <code>~/.local/share/JetBrains/Toolbox/scripts/pycharm</code>
                        ）；GUI 启动的 VS Code 可能找不到裸
                        {' '}
                        <code>pycharm</code>
                        ，填全路径最稳。
                      </>
                    )}
                  >
                    <input
                      type="text"
                      value={pycharmCommand}
                      onChange={e => setPycharmCommand(e.target.value)}
                      placeholder="pycharm"
                      className={inputClass}
                    />
                  </Field>
                )}

                <Field
                  label="worktree 创建后脚本"
                  hint={(
                    <>
                      工单进入"实施"后，
                      <code>git worktree add</code>
                      {' '}
                      成功立刻执行。留空 = 默认
                      {' '}
                      <code>.spx/worktree-post-create.sh</code>
                      ；文件不存在 = 静默跳过，不报错。
                      <br />
                      环境变量：
                      <code>WORKTREE_PATH</code>
                      、
                      <code>WORKSPACE_ROOT</code>
                      、
                      <code>BRANCH</code>
                      、
                      <code>ISSUE_NUMBER</code>
                      、
                      <code>MAIN_BRANCH</code>
                      。超时 30s。
                    </>
                  )}
                >
                  <input
                    type="text"
                    value={worktreePostCreateScript}
                    onChange={e => setWorktreePostCreateScript(e.target.value)}
                    placeholder=".spx/worktree-post-create.sh"
                    className={inputClass}
                  />
                </Field>

                <Field
                  label="worktree 删除前脚本"
                  hint={(
                    <>
                      工单关闭 / 手动删 worktree 时，
                      <code>git worktree remove</code>
                      {' '}
                      之前执行，便于关闭 IDE / 归档日志。留空 = 默认
                      {' '}
                      <code>.spx/worktree-pre-remove.sh</code>
                      。失败不阻塞删除。
                    </>
                  )}
                >
                  <input
                    type="text"
                    value={worktreePreRemoveScript}
                    onChange={e => setWorktreePreRemoveScript(e.target.value)}
                    placeholder=".spx/worktree-pre-remove.sh"
                    className={inputClass}
                  />
                </Field>

                <Field
                  label="实施 tab 创建前脚本"
                  hint={(
                    <>
                      实施 cc tab 创建之前执行；常用于启动 IDE（pycharm 等）。
                      留空 = 默认
                      {' '}
                      <code>.spx/impl-tab-pre-create.sh</code>
                      。tab 复用 fast path 不触发。
                    </>
                  )}
                >
                  <input
                    type="text"
                    value={implTabPreCreateScript}
                    onChange={e => setImplTabPreCreateScript(e.target.value)}
                    placeholder=".spx/impl-tab-pre-create.sh"
                    className={inputClass}
                  />
                </Field>

                <Field
                  label="实施 tab 关闭后脚本"
                  hint={(
                    <>
                      实施 cc tab 被关闭后执行；常用于关闭 pre-create 启动的 IDE 进程。
                      留空 = 默认
                      {' '}
                      <code>.spx/impl-tab-post-close.sh</code>
                      。
                    </>
                  )}
                >
                  <input
                    type="text"
                    value={implTabPostCloseScript}
                    onChange={e => setImplTabPostCloseScript(e.target.value)}
                    placeholder=".spx/impl-tab-post-close.sh"
                    className={inputClass}
                  />
                </Field>
              </>
            )}
          </div>
        </div>

        {/* Footer */}
        <div className="flex items-center justify-end gap-2 border-t border-[var(--vscode-panel-border)] p-3">
          {canCancel && onCancel && (
            <button
              type="button"
              onClick={onCancel}
              className="rounded border border-[var(--vscode-button-border,transparent)] bg-[var(--vscode-button-secondaryBackground)] px-3 py-1.5 text-xs text-[var(--vscode-button-secondaryForeground)] hover:bg-[var(--vscode-button-secondaryHoverBackground)]"
            >
              取消
            </button>
          )}
          <button
            type="submit"
            className="rounded bg-[var(--vscode-button-background)] px-3 py-1.5 text-xs text-[var(--vscode-button-foreground)] hover:bg-[var(--vscode-button-hoverBackground)]"
          >
            保存
          </button>
        </div>
      </form>
    </div>
  )
}
