import { useEffect, useState } from 'react'
import { open as openDialog } from '@tauri-apps/plugin-dialog'
import {
  activeSessionCount,
  claudePathInfo,
  claudePathTest,
  claudeUpdate,
  contextMenuStatus,
  hooksStatus,
  indexRebuild,
  indexStatus,
  installContextMenu,
  installHooks,
  uninstallContextMenu,
  uninstallHooks,
  mobileStatus,
  mobileRelayPairingUrl,
  mobileUnbind,
  updateSettings,
  wechatStartLogin,
  wechatStatus,
  wechatUnbind
} from '../ipc'
import type { MobileStatus } from '../ipc'
import type { ClaudePathInfo, Config, IndexStatus, Settings, WechatStatus } from '../types'

/** 微信登录阶段的展示文案 */
const WECHAT_PHASE_TEXT: Record<string, string> = {
  off: '未开始',
  await_scan: '等待扫码',
  scanned: '已扫码，待手机确认',
  bound: '已绑定',
  expired: '二维码已过期',
  error: '出错'
}

/** 解析来源的展示文案 */
const SOURCE_LABELS: Record<ClaudePathInfo['source'], string> = {
  manual: '手动指定',
  'manual-invalid': '路径无效，已回退自动检测',
  path: 'PATH 解析',
  known: '常见安装位置',
  fallback: 'cmd 兜底'
}

interface Props {
  config: Config
  onConfigChange: (c: Config) => void
}

export function SettingsPage({ config, onConfigChange }: Props) {
  const settings = config.settings
  const [menuInstalled, setMenuInstalled] = useState(false)
  const [hooksInstalled, setHooksInstalled] = useState(false)
  const [pathInfo, setPathInfo] = useState<ClaudePathInfo | null>(null)
  const [pathText, setPathText] = useState(settings.claudePath)
  const [testResult, setTestResult] = useState<{ ok: boolean; text: string } | null>(null)
  const [testing, setTesting] = useState(false)
  const [portText, setPortText] = useState(String(settings.hookPort))
  const [relayUrlText, setRelayUrlText] = useState(settings.mobileRelayUrl)
  const [error, setError] = useState('')
  const [runningCount, setRunningCount] = useState(0)
  const [updating, setUpdating] = useState(false)
  const [updateResult, setUpdateResult] = useState<{ ok: boolean; text: string } | null>(null)
  const [indexStatuses, setIndexStatuses] = useState<Record<string, IndexStatus>>({})
  const [wx, setWx] = useState<WechatStatus | null>(null)
  const [mob, setMob] = useState<MobileStatus | null>(null)
  const [relayPair, setRelayPair] = useState<string>('')

  useEffect(() => {
    void contextMenuStatus().then(setMenuInstalled)
    void hooksStatus().then(setHooksInstalled)
    void claudePathInfo().then(setPathInfo)
    void activeSessionCount().then(setRunningCount)
  }, [])

  // 开启微信对接时轮询绑定状态（扫码进度 / 已绑定 / 操作人是否激活）
  useEffect(() => {
    if (!settings.wechatEnabled) {
      setWx(null)
      return
    }
    let alive = true
    const poll = async () => {
      const s = await wechatStatus()
      if (alive) setWx(s)
    }
    void poll()
    const timer = setInterval(() => void poll(), 1500)
    return () => {
      alive = false
      clearInterval(timer)
    }
  }, [settings.wechatEnabled])

  /** 开始扫码：取二维码并起后台轮询，立即刷新状态以显示二维码 */
  const startWxLogin = async () => {
    setError('')
    try {
      await wechatStartLogin()
      setWx(await wechatStatus())
    } catch (e) {
      setError(String(e))
    }
  }

  /** 解绑微信：停后台线程、删凭据 */
  const unbindWx = async () => {
    try {
      await wechatUnbind()
      setWx(await wechatStatus())
    } catch (e) {
      setError(String(e))
    }
  }

  // 开启移动端网关或公网中继时轮询状态（本机地址 / 令牌 / 已连设备数 / 中继在线）
  useEffect(() => {
    if (!settings.mobileEnabled && !settings.mobileRelayEnabled) {
      setMob(null)
      return
    }
    let alive = true
    const poll = async () => {
      const s = await mobileStatus()
      if (alive) setMob(s)
    }
    void poll()
    const timer = setInterval(() => void poll(), 2000)
    return () => {
      alive = false
      clearInterval(timer)
    }
  }, [settings.mobileEnabled, settings.mobileRelayEnabled])

  // 中继开启且地址已填时，取配对链接（https://relay/?room=..&token=..）
  useEffect(() => {
    if (!settings.mobileRelayEnabled || !settings.mobileRelayUrl.trim()) {
      setRelayPair('')
      return
    }
    let alive = true
    void mobileRelayPairingUrl().then((u) => {
      if (alive) setRelayPair(u)
    })
    return () => {
      alive = false
    }
  }, [settings.mobileRelayEnabled, settings.mobileRelayUrl, mob?.relayRoom])

  /** 重置移动端令牌（旧配对失效；现有连接需关闭再开启开关才断开） */
  const unbindMobile = async () => {
    try {
      await mobileUnbind()
      setMob(await mobileStatus())
    } catch (e) {
      setError(String(e))
    }
  }

  // 开启索引时轮询各项目的索引状态（扫描在后台线程进行）
  useEffect(() => {
    if (!settings.indexEnabled) {
      setIndexStatuses({})
      return
    }
    let alive = true
    const poll = async () => {
      const entries = await Promise.all(
        config.projects.map(async (p) => [p.path, await indexStatus(p.path)] as const)
      )
      if (alive) setIndexStatuses(Object.fromEntries(entries))
    }
    void poll()
    const timer = setInterval(() => void poll(), 1500)
    return () => {
      alive = false
      clearInterval(timer)
    }
  }, [settings.indexEnabled, config.projects])

  const rebuildIndex = async (path: string) => {
    setError('')
    try {
      await indexRebuild(path)
    } catch (e) {
      setError(String(e))
    }
  }

  /** 集中手动更新 Claude Code：仅在无运行会话时由后端放行 */
  const runUpdate = async () => {
    setUpdating(true)
    setUpdateResult(null)
    try {
      const text = await claudeUpdate()
      setUpdateResult({ ok: true, text })
      setPathInfo(await claudePathInfo())
    } catch (e) {
      setUpdateResult({ ok: false, text: String(e) })
    } finally {
      setUpdating(false)
      setRunningCount(await activeSessionCount())
    }
  }

  const apply = async (patch: Partial<Settings>) => {
    setError('')
    try {
      onConfigChange(await updateSettings({ ...settings, ...patch }))
    } catch (e) {
      setError(String(e))
    }
  }

  /** 保存 claude 路径并刷新解析结果 */
  const applyClaudePath = async (value: string) => {
    setTestResult(null)
    await apply({ claudePath: value })
    setPathInfo(await claudePathInfo())
  }

  const pickClaude = async () => {
    const picked = await openDialog({
      title: '选择 claude 可执行文件',
      filters: [{ name: '可执行文件', extensions: ['exe', 'cmd', 'bat'] }]
    })
    if (typeof picked === 'string' && picked) {
      setPathText(picked)
      await applyClaudePath(picked)
    }
  }

  const runPathTest = async () => {
    setTesting(true)
    setTestResult(null)
    try {
      const version = await claudePathTest()
      setTestResult({ ok: true, text: `可用：${version}` })
    } catch (e) {
      setTestResult({ ok: false, text: `不可用：${String(e)}` })
    } finally {
      setTesting(false)
    }
  }

  const Toggle = ({ value, onChange }: { value: boolean; onChange: (v: boolean) => void }) => (
    <button className={`switch ${value ? 'on' : ''}`} onClick={() => onChange(!value)} />
  )

  return (
    <div className="page" style={{ maxWidth: 720 }}>
      <h2>设置</h2>
      <p className="page-desc">外观、系统集成与通知</p>
      {error && <div style={{ color: 'var(--danger)', marginBottom: 10 }}>{error}</div>}

      <div className="settings-group">
        <div className="settings-row">
          <div className="settings-label">
            主题
            <div className="sub">跟随系统会随 Windows 深浅色模式自动切换</div>
          </div>
          <select
            className="select"
            value={settings.theme}
            onChange={(e) => void apply({ theme: e.target.value as Settings['theme'] })}
          >
            <option value="system">跟随系统</option>
            <option value="light">浅色</option>
            <option value="dark">深色</option>
          </select>
        </div>
        <div className="settings-row">
          <div className="settings-label">
            关闭按钮最小化到托盘
            <div className="sub">关闭时不退出，终端会话保持运行；从托盘图标恢复或退出</div>
          </div>
          <Toggle value={settings.closeToTray} onChange={(v) => void apply({ closeToTray: v })} />
        </div>
      </div>

      <div className="settings-group">
        <div className="settings-row">
          <div className="settings-label">
            资源管理器右键菜单
            <div className="sub">
              在文件夹右键菜单添加「在 CCGUI 中打开」（Win11 位于“显示更多选项”内）
            </div>
          </div>
          <span className="badge">{menuInstalled ? '已添加' : '未添加'}</span>
          <button
            className="btn"
            onClick={async () => {
              try {
                if (menuInstalled) {
                  await uninstallContextMenu()
                } else {
                  await installContextMenu()
                }
                setMenuInstalled(await contextMenuStatus())
              } catch (e) {
                setError(String(e))
              }
            }}
          >
            {menuInstalled ? '移除' : '添加'}
          </button>
        </div>
      </div>

      <div className="settings-group">
        <div className="settings-row">
          <div className="settings-label">
            会话状态感知（hooks）
            <div className="sub">
              在 ~/.claude/settings.json 登记 hooks，上报会话状态用于标签状态点与系统通知。
              对所有 Claude Code 会话生效（包括 GUI 外启动的，GUI 未运行时上报会被忽略）
            </div>
          </div>
          <span className="badge">{hooksInstalled ? '已登记' : '未登记'}</span>
          <button
            className="btn"
            onClick={async () => {
              try {
                if (hooksInstalled) {
                  await uninstallHooks()
                } else {
                  await installHooks()
                }
                setHooksInstalled(await hooksStatus())
              } catch (e) {
                setError(String(e))
              }
            }}
          >
            {hooksInstalled ? '移除' : '登记'}
          </button>
        </div>
        <div className="settings-row">
          <div className="settings-label">
            状态上报端口
            <div className="sub">修改后需重启 CCGUI 并重新登记 hooks</div>
          </div>
          <input
            className="input"
            style={{ width: 90 }}
            value={portText}
            onChange={(e) => setPortText(e.target.value)}
            onBlur={() => {
              const port = Number(portText)
              if (Number.isInteger(port) && port > 1024 && port < 65536) {
                void apply({ hookPort: port })
              } else {
                setPortText(String(settings.hookPort))
              }
            }}
          />
        </div>
        <div className="settings-row">
          <div className="settings-label">
            等待输入时通知
            <div className="sub">窗口失焦时，会话等待确认/输入则发送系统通知</div>
          </div>
          <Toggle
            value={settings.notifyOnAttention}
            onChange={(v) => void apply({ notifyOnAttention: v })}
          />
        </div>
        <div className="settings-row">
          <div className="settings-label">
            回合结束时通知
            <div className="sub">窗口失焦时，会话完成一轮任务则发送系统通知</div>
          </div>
          <Toggle value={settings.notifyOnDone} onChange={(v) => void apply({ notifyOnDone: v })} />
        </div>
      </div>

      <div className="settings-group">
        <div className="settings-row">
          <div className="settings-label">
            claude 可执行文件
            <div className="sub">留空则自动检测：PATH 解析 → 常见安装位置 → cmd /c 兜底</div>
          </div>
          <input
            className="input"
            style={{ width: 280, fontFamily: 'var(--font-mono)' }}
            placeholder="自动检测"
            value={pathText}
            onChange={(e) => setPathText(e.target.value)}
            onBlur={() => {
              if (pathText.trim() !== settings.claudePath) void applyClaudePath(pathText.trim())
            }}
          />
          <button className="btn" onClick={() => void pickClaude()}>
            浏览
          </button>
        </div>
        <div className="settings-row">
          <div className="settings-label">
            当前解析结果
            <div className="sub" style={{ fontFamily: 'var(--font-mono)' }}>
              {pathInfo ? pathInfo.display : '解析中…'}
            </div>
            {testResult && (
              <div className="sub" style={{ color: testResult.ok ? 'var(--ok)' : 'var(--danger)' }}>
                {testResult.text}
              </div>
            )}
          </div>
          {pathInfo && <span className="badge">{SOURCE_LABELS[pathInfo.source]}</span>}
          <button className="btn" disabled={testing} onClick={() => void runPathTest()}>
            {testing ? '检测中…' : '检测'}
          </button>
        </div>
        <div className="settings-row">
          <div className="settings-label">
            Claude Code 更新
            <div className="sub">
              CCGUI 启动的会话已禁用后台自动更新（避免多会话并发时锁住 claude.exe 报“in use”）。
              更新请在此集中进行，需先关闭所有终端标签。
            </div>
            {updateResult && (
              <div
                className="sub"
                style={{
                  color: updateResult.ok ? 'var(--ok)' : 'var(--danger)',
                  whiteSpace: 'pre-wrap'
                }}
              >
                {updateResult.text}
              </div>
            )}
          </div>
          {runningCount > 0 && <span className="badge">{runningCount} 个会话运行中</span>}
          <button
            className="btn"
            disabled={updating || runningCount > 0}
            onClick={() => void runUpdate()}
          >
            {updating ? '更新中…' : '立即更新'}
          </button>
        </div>
      </div>

      <div className="settings-group">
        <div className="settings-row">
          <div className="settings-label">
            代码索引（符号图谱）
            <div className="sub">
              用 tree-sitter 为项目建立符号/引用/导入索引，并在 项目/.mcp.json 注册 ccgui-index
              服务器，供 Claude Code 调用，更精确地查找定义与引用。开启后新会话会请求确认该 MCP
              服务器；首次为每个项目后台全量扫描，之后随文件改动增量更新。
            </div>
          </div>
          <Toggle value={settings.indexEnabled} onChange={(v) => void apply({ indexEnabled: v })} />
        </div>
        {settings.indexEnabled &&
          config.projects.map((p) => {
            const st = indexStatuses[p.path]
            const text = !st
              ? '等待中…'
              : st.indexing
                ? '索引中…'
                : st.lastError
                  ? `出错：${st.lastError}`
                  : `${st.files} 文件 / ${st.symbols} 符号`
            return (
              <div className="settings-row" key={p.path}>
                <div className="settings-label">
                  {p.name}
                  <div className="sub" style={{ fontFamily: 'var(--font-mono)' }}>
                    {text}
                  </div>
                </div>
                <button
                  className="btn"
                  disabled={st?.indexing}
                  onClick={() => void rebuildIndex(p.path)}
                >
                  {st?.indexing ? '索引中' : '重建'}
                </button>
              </div>
            )
          })}
        {settings.indexEnabled && config.projects.length === 0 && (
          <div className="settings-row">
            <div className="settings-label">
              <div className="sub">尚无项目；添加项目后将自动建立索引。</div>
            </div>
          </div>
        )}
      </div>

      <div className="settings-group">
        <div className="settings-row">
          <div className="settings-label">
            微信 ClawBot 远程通知与遥控
            <div className="sub">
              通过微信官方 ClawBot 插件，把各会话的待处理/完成/额度提醒推送到微信；引用通知回复可
              远程入队指令（通知带 #编号，回复精确路由到对应会话）。依赖会话状态感知（hooks）。
              协议为社区逆向、非官方公开接口，可能随微信更新变动。
            </div>
          </div>
          <Toggle value={settings.wechatEnabled} onChange={(v) => void apply({ wechatEnabled: v })} />
        </div>
        {settings.wechatEnabled && (
          <>
            <div className="settings-row">
              <div className="settings-label">
                绑定状态
                <div className="sub">
                  {wx?.bound
                    ? `已绑定${wx.nick ? `：${wx.nick}` : ''}${
                        wx.operatorActive ? '（已激活，可推送）' : '（请在微信给 bot 发一条消息以激活推送）'
                      }`
                    : (WECHAT_PHASE_TEXT[wx?.phase ?? 'off'] ?? '未开始')}
                </div>
                {wx?.message && (
                  <div className="sub" style={{ color: 'var(--text-faint)' }}>
                    {wx.message}
                  </div>
                )}
              </div>
              {wx?.bound ? (
                <button className="btn" onClick={() => void unbindWx()}>
                  解绑
                </button>
              ) : (
                <button className="btn" onClick={() => void startWxLogin()}>
                  开始扫码
                </button>
              )}
            </div>
            {!wx?.bound && wx?.qrDataUrl && (
              <div className="settings-row">
                <div className="settings-label">
                  扫码绑定
                  <div className="sub">
                    用微信扫描二维码（需已开通 ClawBot 插件）；过期可重新点「开始扫码」
                  </div>
                </div>
                <img
                  src={wx.qrDataUrl}
                  alt="微信登录二维码"
                  width={180}
                  height={180}
                  style={{ background: '#fff', padding: 6, borderRadius: 6 }}
                />
              </div>
            )}
            <div className="settings-row">
              <div className="settings-label">
                允许微信遥控
                <div className="sub">关闭则只接收通知、不接受从微信发来的指令</div>
              </div>
              <Toggle
                value={settings.wechatRemoteInput}
                onChange={(v) => void apply({ wechatRemoteInput: v })}
              />
            </div>
            <div className="settings-row">
              <div className="settings-label">推送：等待权限确认</div>
              <Toggle
                value={settings.wechatPushAttention}
                onChange={(v) => void apply({ wechatPushAttention: v })}
              />
            </div>
            <div className="settings-row">
              <div className="settings-label">推送：回合完成摘要</div>
              <Toggle
                value={settings.wechatPushDone}
                onChange={(v) => void apply({ wechatPushDone: v })}
              />
            </div>
            <div className="settings-row">
              <div className="settings-label">推送：账号额度告警</div>
              <Toggle
                value={settings.wechatPushQuota}
                onChange={(v) => void apply({ wechatPushQuota: v })}
              />
            </div>
          </>
        )}
      </div>

      <div className="settings-group">
        <div className="settings-row">
          <div className="settings-label">
            移动端 App 远程映射
            <div className="sub">
              开启后在局域网提供 WebSocket 网关，手机 App 连上即可实时镜像终端、遥控会话、
              原生响应 AI 选项。仅监听局域网，需与电脑同一 WiFi；首次启用请在防火墙放行端口。
            </div>
          </div>
          <Toggle value={settings.mobileEnabled} onChange={(v) => void apply({ mobileEnabled: v })} />
        </div>
        {settings.mobileEnabled && (
          <>
            <div className="settings-row">
              <div className="settings-label">
                连接地址
                <div className="sub">在手机 App 中填入以下地址与配对令牌（需同一 WiFi）</div>
                {mob && (
                  <div className="sub" style={{ color: 'var(--text-faint)', userSelect: 'text' }}>
                    令牌：{mob.token}
                    <br />本机自测：ws://127.0.0.1:{mob.port}
                    {mob.localIps.map((ip) => (
                      <span key={ip}>
                        <br />
                        手机（同 WiFi，逐个试）：ws://{ip}:{mob.port}
                      </span>
                    ))}
                  </div>
                )}
              </div>
              <button className="btn" onClick={() => void unbindMobile()}>
                重置令牌
              </button>
            </div>
            <div className="settings-row">
              <div className="settings-label">
                已连接设备
                <div className="sub">当前实时连接到本机的移动端数量</div>
              </div>
              <div className="sub">{mob?.connCount ?? 0}</div>
            </div>
          </>
        )}

        <div className="settings-row">
          <div className="settings-label">
            移动端文件管理
            <div className="sub">
              开启后手机端出现「文件」入口，可管理电脑文件：新建 / 重命名 / 编辑 / 复制 / 移动 /
              打包解压 / 删除（一律进回收站，可恢复）。手机令牌泄露 = 本机文件可被篡改，默认关闭，用时再开。
            </div>
          </div>
          <Toggle value={settings.mobileFsEnabled} onChange={(v) => void apply({ mobileFsEnabled: v })} />
        </div>

        <div className="settings-row">
          <div className="settings-label">
            公网中继（外网访问）
            <div className="sub">
              CCGUI 主动连到你的境内 VPS 中继，手机在外网（4G / 异地 WiFi）即可远程访问，电脑无需开放入站端口。
              注意：本方案不做端到端加密，终端内容对中继服务器可见，请部署在你自己信任的 VPS。
            </div>
          </div>
          <Toggle
            value={settings.mobileRelayEnabled}
            onChange={(v) => void apply({ mobileRelayEnabled: v })}
          />
        </div>
        {settings.mobileRelayEnabled && (
          <>
            <div className="settings-row">
              <div className="settings-label">
                中继地址
                <div className="sub">形如 wss://你的域名/（由前置 Caddy 提供 HTTPS，改后自动重连）</div>
              </div>
              <input
                className="input"
                style={{ width: 280, fontFamily: 'var(--font-mono)' }}
                placeholder="wss://你的域名/"
                value={relayUrlText}
                onChange={(e) => setRelayUrlText(e.target.value)}
                onBlur={() => {
                  const v = relayUrlText.trim()
                  if (v !== settings.mobileRelayUrl) void apply({ mobileRelayUrl: v })
                }}
              />
            </div>
            <div className="settings-row">
              <div className="settings-label">
                手机配对
                <div className="sub">
                  手机浏览器打开下面链接即自动连回本机；也可在手机「中继（外网）」里手动填：域名 + 房间号 + 令牌
                </div>
                {mob && (
                  <div
                    className="sub"
                    style={{ color: 'var(--text-faint)', userSelect: 'text', wordBreak: 'break-all' }}
                  >
                    房间号：{mob.relayRoom || '—'}
                    <br />
                    令牌：{mob.token}
                  </div>
                )}
                {relayPair && (
                  <div
                    className="sub"
                    style={{ color: 'var(--text-faint)', userSelect: 'text', wordBreak: 'break-all' }}
                  >
                    配对链接：{relayPair}
                  </div>
                )}
              </div>
              <div className="sub">{mob?.relayOnline ? '已拨通' : '未连接'}</div>
            </div>
          </>
        )}
      </div>
    </div>
  )
}
