const steps = [
  { key: "welcome", title: "欢迎", caption: "了解教练，开启体验" },
  { key: "profile", title: "建档", caption: "完善你的主播身份" },
  { key: "coach", title: "教练", caption: "开启 AI 教练陪练" }
];

const navSteps = [
  { title: "欢迎", index: 0, group: [0] },
  { title: "设置", index: 1, group: [1] },
  { title: "教练", index: 5, group: [5] },
  { title: "智能机架", index: 6, group: [6], alwaysAvailable: true },
  { title: "视频号采集", index: 7, group: [7], alwaysAvailable: true },
  { title: "下播承接", index: 8, group: [8], alwaysAvailable: true }
];

const setupSubSteps = [];

const form = {
  anchor_id: "",
  anchor_name: "",
  platform: "通用直播平台",
  anchor_type: "",
  main_persona: "",
  supporting_traits: "",
  contrast_persona: "",
  relationship_feel: "",
  interaction_modes: "",
  speaking_rhythms: "",
  disliked_expressions: "",
  anchor_stage: "不参与提醒判断",
  baseline_goals: "",
  strengths: "",
  weaknesses: "",
  taboos: "",
  interaction_preference: "",
  style_preference: "低打扰只报关键节点",
  today_theme: "",
  today_goal: "少打扰高命中",
  today_rhythm: "",
  today_do: "",
  today_avoid: "",
  key_users: "",
  suggestion_style: "低打扰关键节点 / 自然口语式",
  relay_url: "http://127.0.0.1:8787",
  room: "demo",
  relay_key: "",
  vision_semantic_enabled: false
};

const fieldTips = {
  anchor_name: "建议填写主播常用昵称，AI 会用它建立稳定档案。",
  anchor_id: "同一位主播尽量保持同一个 ID，方便长期追踪成长变化。",
  platform: "平台会影响评论节奏、礼物表达和常见直播玩法。",
  main_persona: "只选一个。它决定主播稳定的情绪底色和说话方式。",
  supporting_traits: "只选一个。它决定这句话怎样把情绪外放给观众听见。",
  relationship_feel: "只选一个。它决定主播与观众靠近时的距离感。",
  disliked_expressions: "这些表达不会改变本地触发，只用于避免写成主播不喜欢的口吻。",
  relay_url: "主播电脑内部地址，测试版固定只走本机，不上传直播事件。",
  room: "本机采集、分析和悬浮提醒使用同一个内部房间。",
  relay_key: "本机内部通道可留空；测试包不会保存主控端密钥。",
  vision_semantic_enabled: "可选的本机低频画面理解；默认关闭，不影响核心提醒链路。"
};

const optionGroups = {
  platform: ["通用直播平台", "视频号", "抖音", "快手", "小红书", "其他平台"],
  main_persona: [
    "直球热烈", "甜酷反差", "嘴硬心软", "元气带动",
    "御姐掌场", "松弛搞笑", "温柔走心", "清冷回撩"
  ],
  supporting_traits: [
    "惊喜放大", "走心被打动", "俏皮逗弄", "爽朗带动",
    "冷感回撩", "委屈撒娇", "认真护短", "戏剧化反应"
  ],
  contrast_persona: [
    "嘴硬心软", "外冷内热", "傲娇可爱", "御姐撒娇", "清冷反差", "甜酷反差", "强势护短", "理性浪漫",
    "慢热真诚", "表面嫌弃实际在意", "平时松弛关键时刻认真", "平时温柔PK时有胜负欲", "看起来成熟偶尔幼稚",
    "说话直接但很会照顾情绪", "不擅长肉麻但会记住细节", "不主动讨好但收到支持会认真回应"
  ],
  relationship_feel: [
    "朋友式亲近", "宠粉偏爱", "姐姐带队", "若即若离"
  ],
  interaction_modes: [
    "认真倾听", "具体回应", "顺势接梗", "轻怼反打", "装傻化解", "自嘲破冰", "观点回应", "故事延续", "情绪安抚", "细节关心",
    "轻松追问", "低压力互动", "把个人话题扩展到全场", "给观众存在感", "给面但不抬得过高", "护短但不攻击别人", "稳场控场", "温柔带队",
    "仪式感承接", "支持转成全场情绪", "幽默化解尴尬", "尊重不想被过度关注的人", "体面送别", "留白停顿"
  ],
  speaking_rhythms: [
    "短句直接", "温柔慢说", "慢半拍再回应", "轻声克制", "爽朗利落", "俏皮收尾", "先认真后玩笑", "先给面再回主线",
    "先听完再接话", "情绪明显但不过度", "有观点但不说教", "轻松不刻意", "不连续追问", "不长篇感谢", "说到点上就收", "留一点想象空间"
  ],
  disliked_expressions: [
    "过度肉麻", "客服式感谢", "运营老师口吻", "命令观众扣字", "连续追问", "长篇感谢", "过度撒娇", "强行装熟",
    "夸张煽情", "网络土味称呼", "硬催礼物", "攻击对面主播", "解释内部数据", "机械固定开头"
  ]
};

const selectionLimits = {
  supporting_traits: 1,
  contrast_persona: 1,
  interaction_modes: 2,
  speaking_rhythms: 2
};

const initialPage = new URLSearchParams(window.location.search).get("page");
let activeStep = initialPage === "smart-rack" ? 6
  : initialPage === "wechat-collector" ? 7
  : initialPage === "post-live-coach" ? 8
  : 0;
let unlockedStep = 0;
let setupComplete = false;
let state = {};
let latest = null;
let smartRackStatus = {
  connection: "正在检测",
  tone: "checking",
  mode: "none",
  optional: true,
  ready: true,
  checked_at: "—",
  voltune: { connected: false, installed: true, selected: false, label: "未启用", detail: "主播可按需启用" },
  soundstage: { connected: false, installed: true, selected: false, label: "未启用", detail: "主播可按需启用" },
  audio_devices: {
    microphone: { detected: false, label: "检测中", detail: "正在读取 Windows 音频设备" },
    virtual_cable: { detected: false, label: "检测中", detail: "正在读取 Windows 音频设备" },
    output: { detected: false, label: "检测中", detail: "正在读取 Windows 音频设备" }
  }
};
let aiSongStatus = {
  installed: false,
  running: false,
  process_id: 0,
  available_memory_gb: 0,
  minimum_memory_gb: 4,
  memory_ready: false,
  root_directory: "D:\\AISongStudioMVP",
  worker_assembly_path: "D:\\AISongStudioMVP\\.builds\\headless-worker\\AISongStudio.Local.dll",
  recordings_directory: "D:\\AISongStudioMVP\\data\\recordings"
};
let wechatCollectorStatus = {
  collector_open: false,
  collector_attached: false,
  collector_receiving: false,
  collector_state: "not_started",
  collector_state_label: "尚未启动采集",
  collector_status: "尚未打开视频号采集",
  analysis_status: "正在检测本地分析引擎",
  analysis_running: false,
  microphone_detected: false,
  microphone_state: "unknown",
  microphone_state_label: "正在检测麦克风与转写引擎",
  voiceprint_configured: false,
  local_only: true,
  viewers: 0,
  cumulative_viewers: 0,
  comments: 0,
  cumulative_comments: 0,
  recent_comments: [],
  gifts: 0,
  heat: 0,
  updated_at_ms: 0
};
let pageNotice = { message: "", kind: "info" };
let runtimeConfigApplied = false;

const $ = (selector) => document.querySelector(selector);

window.LiveCoachNative = {
  receive(message) {
    const { type, payload } = message || {};
    if (type === "state") {
      state = payload || {};
      setupComplete = !!state.setup_complete;
      if (setupComplete) unlockedStep = 5;
      applyRuntimeConfig();
      applyProfileState();
      render();
    }
    if (type === "saveResult") {
      showToast(payload?.message || "已保存");
      if (payload?.mode === "baseline") {
        setupComplete = true;
        unlockedStep = 5;
      }
      if (payload?.mode === "plan") {
        unlockedStep = 5;
      }
      render();
    }
    if (type === "latest") {
      latest = payload;
      if (activeStep === 5) render();
    }
    if (type === "smartRackStatus") {
      smartRackStatus = payload || smartRackStatus;
      if (activeStep === 0 || activeStep === 5 || activeStep === 6 || activeStep === 7) render();
    }
    if (type === "aiSongStatus") {
      aiSongStatus = payload || aiSongStatus;
      if (activeStep === 10) render();
    }
    if (type === "wechatCollectorStatus") {
      const previousCollectorState = wechatCollectorStatus.collector_state;
      wechatCollectorStatus = payload || wechatCollectorStatus;
      if (activeStep === 0 || activeStep === 5 || activeStep === 7) render();
      if (
        ["session_lost", "stalled"].includes(wechatCollectorStatus.collector_state) &&
        !["session_lost", "stalled"].includes(previousCollectorState)
      ) {
        showToast("直播数据采集已停止：视频号登录可能被其他页面顶掉，请点击“直播数据”检查登录");
      }
    }
    if (type === "error") {
      showToast(payload?.message || "发生错误");
    }
    if (type === "toast") {
      showToast(payload?.message || "已完成");
    }
  }
};

function post(type, payload = {}) {
  if (window.chrome?.webview) {
    window.chrome.webview.postMessage({ type, payload });
  }
}

function render() {
  document.body.classList.toggle("coach-mode", activeStep === 5);
  document.body.classList.toggle("welcome-mode", activeStep === 0);
  document.body.classList.toggle("smart-rack-mode", activeStep === 6);
  document.body.classList.toggle("wechat-collector-mode", activeStep === 7);
  document.body.classList.toggle("post-live-mode", activeStep === 8);
  renderRail();
  renderPrep();
  renderTrust();
  renderStage();
}

function renderRail() {
  $("#step-rail").innerHTML = navSteps.map((step) => {
    const done = step.group.every((index) => setupComplete && index <= 3 || index < activeStep);
    const active = step.group.includes(activeStep);
    const cls = ["step", active ? "active" : "", done ? "done" : ""].join(" ");
    const disabled = !step.alwaysAvailable && step.index > unlockedStep;
    return `
      <button class="${cls}" data-step="${step.index}" ${disabled ? "disabled" : ""}>
        <span class="step-number"></span>
        <span class="step-copy">
          <span class="step-title">${step.title}</span>
        </span>
      </button>
    `;
  }).join("");

  document.querySelectorAll(".step").forEach((button) => {
    button.addEventListener("click", () => {
      const next = Number(button.dataset.step);
      const target = navSteps.find((step) => step.index === next);
      if (target?.alwaysAvailable || next <= unlockedStep) {
        activeStep = next;
        render();
        if (next === 6) post("getSmartRackStatus");
        if (next === 7) post("getWeChatCollectorStatus");
      }
    });
  });
}

function setupProgress() {
  if (activeStep < 1 || activeStep > 3) return "";
  return `
    <div class="setup-progress" aria-label="建档进度">
      <span class="setup-progress-title">主播建档</span>
      ${setupSubSteps.map((step) => {
        const cls = ["setup-chip", activeStep === step.index ? "active" : "", activeStep > step.index ? "done" : ""].join(" ");
        return `<span class="${cls}">${step.title}</span>`;
      }).join("")}
    </div>
  `;
}

function renderPrep() {
  const hasProfile = !!state.profile?.has_profile;
  const selectedCount = state.selected_video_count || 0;
  const profileLabel = hasProfile ? "档案已完成" : activeStep > 0 ? "正在设置" : "隐私边界清楚";
  const videoLabel = selectedCount > 0 ? `已选择 ${selectedCount} 个录像` : hasProfile ? "录像已归档" : "录像仅本地保存";
  const syncLabel = latest?.snapshot ? "建议已连接" : "建议可同步";
  $("#prep-card").innerHTML = `
    <h2>今日准备度</h2>
    ${readyItem("&#xE72E;", profileLabel, hasProfile ? "你的主播档案可用" : "你掌控数据与权限")}
    ${readyItem("&#xE714;", videoLabel, selectedCount > 0 ? "保存后进入本地档案" : "你的内容只在本机")}
    ${readyItem("&#xE753;", syncLabel, latest?.snapshot ? "AI 建议可接收" : "AI 建议为你服务")}
    <div class="safe-pill"><span>&#xE72E;</span> 安全 · 可控 · 透明</div>
  `;
}

function readyItem(icon, title, sub) {
  return `
    <div class="ready-item">
      <div class="ready-icon">${icon}</div>
      <div>
        <p class="ready-title">${title}</p>
        <p class="ready-sub">${sub}</p>
      </div>
    </div>
  `;
}

function renderTrust() {
  $("#trust-strip").innerHTML = `
    ${trustItem("&#xE72E;", "不读取账号密码", "保障账号安全与隐私")}
    ${trustItem("&#xE8D7;", "主播端不保存 API Key", "你的密钥只在本地使用")}
    ${trustItem("&#xE895;", "可随时重新建档", "灵活调整，随时开始新的旅程")}
  `;
}

function trustItem(icon, title, sub) {
  return `
    <div class="trust-item">
      <div class="trust-icon">${icon}</div>
      <div>
        <p class="trust-title">${title}</p>
        <p class="trust-sub">${sub}</p>
      </div>
    </div>
  `;
}

function renderStage() {
  const pages = [welcomePage, profilePage, videoPage, surveyPage, planPage, coachPage, smartRackPage, wechatCollectorPage, postLiveCoachPage];
  $("#stage").innerHTML = pages[activeStep]();
  wireInputs();
  wirePageActions();
}

function postLiveCoachPage() {
  return `
    <div class="post-live-page">
      <div class="collector-heading">
        <div>
          <div class="eyebrow">下播后的关系承接</div>
          <h1>把本场真实互动，变成下一次自然连接</h1>
          <p>筛选热度证据达到999的候选，结合弹幕、礼物和互动跨度生成辅导卡。只教主播怎么聊，不自动发送私信。</p>
        </div>
        <div class="collector-local-badge"><span></span>仅本机</div>
      </div>
      <div class="post-live-principles">
        <article><b>先看关系机会</b><p>热度只是候选门槛，不等于消费能力，也不直接决定优先级。</p></article>
        <article><b>一次只做一步</b><p>每张卡只有一个交流目标，先建立自然回应，再看真实反馈。</p></article>
        <article><b>主播手动发送</b><p>所有草稿发送前都由主播确认；未回复不追发，表达边界立即停止。</p></article>
      </div>
      <div class="collector-route">
        <div>
          <small>第一版工作流</small>
          <b>本场日志 → 候选分层 → 私信辅导卡 → 主播手动发送 → 本地记录结果</b>
          <p>个人观看时长、关注/灯牌和历史私信若未采集，会明确显示“暂无证据”，不会由AI猜测。</p>
        </div>
        <strong>不读私信 · 不代发</strong>
      </div>
      <div class="rack-actions">
        <button class="primary" data-open-post-live-coach>打开下播关系承接教练</button>
        <span>测试阶段可先加载模拟直播数据，确认界面和辅导逻辑。</span>
      </div>
    </div>
  `;
}

function wechatCollectorPage() {
  const status = wechatCollectorStatus || {};
  const updated = Number(status.updated_at_ms || 0) > 0
    ? new Date(Number(status.updated_at_ms)).toLocaleTimeString("zh-CN", { hour12: false })
    : "尚未收到";
  return `
    <div class="wechat-collector-page">
      <div class="collector-heading">
        <div>
          <div class="eyebrow">主播电脑本地闭环</div>
          <h1>视频号直播采集</h1>
          <p>在主播端登录一次，直播事件在本机完成采集、分析和提醒；不会上传回主控电脑。</p>
        </div>
        <div class="collector-local-badge"><span></span>仅本机</div>
      </div>

      <div class="collector-status-grid">
        <article class="collector-status-card">
          <small>助手页面</small>
          <b>${escapeHtml(status.collector_status || "尚未打开")}</b>
          <p>${status.collector_open ? "页面可隐藏到后台，采集仍会继续。" : "首次使用需要主播扫码登录视频号助手。"}</p>
        </article>
        <article class="collector-status-card">
          <small>本地分析</small>
          <b>${escapeHtml(status.analysis_status || "正在检测")}</b>
          <p>${status.analysis_running ? `分析进程 ${Number(status.analysis_process_id || 0)}` : "打开采集窗口后会自动检查本地分析组件。"}</p>
        </article>
      </div>

      <div class="collector-metrics">
        ${collectorMetric("当前在线", status.viewers)}
        ${collectorMetric("累计观看", status.cumulative_viewers)}
        ${collectorMetric("近期弹幕", status.comments)}
        ${collectorMetric("当前热度", status.heat)}
      </div>

      <div class="collector-route">
        <div>
          <small>本地数据路径</small>
          <b>视频号助手接口 → 本地规则与 AI → 主播悬浮提醒</b>
          <p>最近数据：${escapeHtml(updated)}　内部房间：${escapeHtml(status.room || "demo")}</p>
        </div>
        <strong>不回传直播事件</strong>
      </div>

      <div class="rack-actions">
        <span>视频号登录与接口状态统一在主程序“数据采集”页中呈现。</span>
        <button class="secondary" data-refresh-wechat-collector>刷新状态</button>
        <span>助手窗口可隐藏；不要在第二个浏览器页面重复登录同一账号。</span>
      </div>
    </div>
  `;
}

function collectorMetric(label, value) {
  return `<div><small>${label}</small><b>${Number(value || 0).toLocaleString("zh-CN")}</b></div>`;
}

function smartRackPage() {
  const status = smartRackStatus || {};
  const voltune = status.voltune || {};
  const soundstage = status.soundstage || {};
  const routeOutput = soundstage.connected
    ? soundstage.detail
    : "连接后显示当前音效输出设备";
  return `
    <div class="smart-rack-page">
      <div class="rack-heading">
        <div>
          <div class="eyebrow">直播日常音频控制</div>
          <h1>智能机架</h1>
          <p>电音、音效与设备状态集中在一个工作台；音频工具异常不会影响 AI 教练。</p>
        </div>
        <div class="rack-connection ${escapeAttr(status.tone || "checking")}">
          <span></span>
          <div><small>当前连接</small><b>${escapeHtml(status.connection || "正在检测")}</b></div>
        </div>
      </div>

      <div class="rack-tool-grid">
        ${rackToolCard("VOLTUNE", "麦克风电音处理", voltune, "电音预设、强度、音高与输入输出设备")}
        ${rackToolCard("SoundStage", "直播音效台", soundstage, "搜索、分类、播放、音量与输出设备")}
      </div>

      <div class="rack-route-card">
        <div class="rack-route-copy">
          <span class="rack-label">直播音频路由</span>
          <b>麦克风经 VOLTUNE 处理，音效由 SoundStage 独立输出</b>
          <p>${escapeHtml(routeOutput)}</p>
        </div>
        <div class="rack-check-time">最近检测<br><b>${escapeHtml(status.checked_at || "—")}</b></div>
      </div>

      <div class="rack-actions">
        <button class="secondary" data-refresh-smart-rack>重新检测</button>
        <span>完整音频控制统一在主程序的音频工作台中呈现。</span>
      </div>
    </div>
  `;
}

function rackToolCard(name, subtitle, tool, capability) {
  const tone = tool.connected ? "online" : tool.installed === false ? "missing" : "offline";
  return `
    <article class="rack-tool-card ${tone}">
      <div class="rack-tool-top">
        <div>
          <span class="rack-label">${escapeHtml(subtitle)}</span>
          <h2>${escapeHtml(name)}</h2>
        </div>
        <span class="rack-tool-state"><i></i>${escapeHtml(tool.label || "检测中")}</span>
      </div>
      <p class="rack-tool-detail" title="${escapeAttr(tool.detail || "")}">${escapeHtml(tool.detail || "正在读取连接状态")}</p>
      <p class="rack-capability">${escapeHtml(capability)}</p>
    </article>
  `;
}

function welcomePage() {
  return `
    <div class="welcome-page">
      <div class="welcome-copy">
        <div class="eyebrow">极简设置，从这里开始</div>
        <h1>设置你的 <span>AI 直播教练</span></h1>
        <p>建立一张稳定的组合人设卡。本地门控先筛选值得分析的现场，豆包再选择当前唯一动作。</p>
        <div class="actions">
          <button class="primary" data-next>开始设置 →</button>
          <button class="secondary" data-preview>稍后了解</button>
        </div>
        <div class="time-note">◇ 整个过程不到 1 分钟</div>
      </div>
      <div class="welcome-visual" aria-hidden="true">
        <div class="motion-orbit">
          <span></span><span></span><span></span>
          <div class="motion-core">AI</div>
        </div>
        <div class="setup-flow">
          <div><b>01</b><span>主人设</span></div>
          <div><b>02</b><span>关系感</span></div>
          <div><b>03</b><span>表达方式</span></div>
        </div>
      </div>
    </div>
  `;
}

function profilePage() {
  return formPage("选择主播表达风格", "只保留真正能改变话术的三项。每条建议都要有观众听得见的情绪起伏。", `
    ${field("主播名称", "anchor_name", "例：小美 / 玖儿")}
    ${field("主播 ID", "anchor_id", "可留空，系统自动生成")}
    ${optionGroup("表达主调", "main_persona", "single", optionGroups.main_persona, "span-2")}
    ${optionGroup("情绪外放动作", "supporting_traits", "single", optionGroups.supporting_traits, "span-2")}
    ${optionGroup("与观众的关系感", "relationship_feel", "single", optionGroups.relationship_feel, "span-2")}
    ${field("Relay 地址", "relay_url", "http://127.0.0.1:8787")}
    ${field("Room", "room", "demo")}
    ${field("连接 Key", "relay_key", "无密钥可留空", "span-2")}
  `, "保存并进入 AI 教练");
}

function videoPage() {
  const count = state.selected_video_count || 0;
  const total = state.selected_video_mb || 0;
  return `
    <div class="form-card">
      <h1>补充画面与录像资料</h1>
      <p>选择最像真实开播状态的片段。AI 会用它理解妆造画面、语气节奏、礼物反馈和冷场处理。</p>
      <div class="video-box">
        <strong>${count > 0 ? `已选择 ${count} 个录像，约 ${total} MB` : "尚未选择录像资料"}</strong>
        <span class="video-count">${count > 0 ? "保存建档时会写入本地档案" : "建议选择 1-3 段日常直播片段"}</span>
        <button class="primary" data-select-video>选择画面/录像资料</button>
      </div>
      <div class="guide-list">
        <span>优先选：能看清妆容背景、有观众互动、有礼物反馈、有冷场处理的片段。</span>
        <span>不用选：纯摆拍、强剪辑成片、和真实开播状态差很多的视频。</span>
      </div>
      ${actions("上一步", "继续策略")}
    </div>
  `;
}

function surveyPage() {
  return formPage("配置高价值触发策略", "不用写作文。这里决定哪些场景值得提醒，哪些弹幕只轻轻带过。", `
    ${optionGroup("可放大的优势", "strengths", "multi", optionGroups.strengths, "", "其他优势")}
    ${optionGroup("影响转化的短板", "weaknesses", "multi", optionGroups.weaknesses, "", "其他短板")}
    ${optionGroup("粉丝经营优先级", "interaction_preference", "multi", optionGroups.interaction_preference, "", "其他经营方式")}
    ${optionGroup("提醒方式/强度", "style_preference", "single", optionGroups.style_preference, "", "其他方式")}
  `, "完成建档");
}

function planPage() {
  return formPage("制定今日关键节点", "只服务今天这场直播。AI 会优先在这些高价值场景触发，而不是固定时间打扰。", `
    ${optionGroup("今日主线", "today_theme", "single", optionGroups.today_theme, "", "自定义主线")}
    ${optionGroup("今日节点目标", "today_goal", "multi", optionGroups.today_goal, "", "其他目标")}
    ${optionGroup("预计直播节奏", "today_rhythm", "single", optionGroups.today_rhythm, "span-2", "其他节奏")}
    ${optionGroup("今天重点触发", "today_do", "multi", optionGroups.today_do, "span-2", "其他触发器")}
    ${optionGroup("今天不要触发", "today_avoid", "multi", optionGroups.today_avoid, "span-2", "其他避免项")}
    ${area("重点用户", "key_users", "例：榜一雪峰哥、常来互动的玖儿、刚送礼或评论很有价值的用户。")}
    ${field("Relay 地址", "relay_url", "http://127.0.0.1:8787")}
    ${field("Room", "room", "demo")}
    ${field("连接 Key", "relay_key", "无密钥可留空")}
  `, "进入 AI 教练");
}

function firstText(...values) {
  for (const value of values) {
    if (typeof value === "string" && value.trim()) return value.trim();
  }
  return "";
}

function coachPage() {
  const snapshot = latest?.snapshot;
  const immediateBenefit = firstText(snapshot?.diagnosis, snapshot?.live_room_diagnosis);
  const followUpDirection = firstText(snapshot?.why_it_matters);
  const deliveryCue = firstText(snapshot?.next_move);
  const advice = firstText(snapshot?.teleprompter_line, snapshot?.advice) || "等待明确高价值事件；本地不会直接生成话术。";
  const viewers = snapshot?.viewers ?? 0;
  const comments = snapshot?.comments ?? 0;
  const gifts = snapshot?.gifts ?? 0;
  const status = latest?.connected === false ? (latest?.status || "连接中断") : "建议同步中";
  return `
    <div class="coach-page">
      <div class="coach-hero-card">
        <div class="coach-header">
          <div>
            <p class="coach-kicker">LiveCoach AI · 实时陪练</p>
            <h1>直播中控台</h1>
          </div>
          <span class="status-pill">${escapeHtml(status)}</span>
        </div>
        <div class="advice-diagnosis-grid">
          <div><span>当前现状</span><b>${escapeHtml(immediateBenefit || "等待足够的直播现场证据。")}</b></div>
          <div><span>可能收益</span><b>${escapeHtml(followUpDirection || "有明确机会后再说明这一步能带来什么。")}</b></div>
          <div><span>何时做 · 看什么</span><b>${escapeHtml(deliveryCue || "正常直播，出现有效反馈后再推进下一步。")}</b></div>
        </div>
        <div class="advice-label">下一步最佳动作</div>
        <div class="advice">${escapeHtml(advice)}</div>
        <div class="coach-actions">
          <button class="secondary" data-open-overlay>打开主播悬浮窗</button>
          <button class="primary" data-refresh>刷新建议</button>
        </div>
      </div>

      <div class="coach-side-grid">
        <section class="coach-panel">
          <div class="panel-title">直播脉搏</div>
          <div class="metric-grid">
            <div><b>${viewers}</b><span>在线</span></div>
            <div><b>${comments}</b><span>评论</span></div>
            <div><b>${gifts}</b><span>礼物</span></div>
          </div>
        </section>
        <section class="coach-panel">
          <div class="panel-title">当前工作方式</div>
          <div class="check-row"><span></span>本地数据只负责判断是否值得提醒</div>
          <div class="check-row"><span></span>组合人设只负责决定这句话怎么说</div>
          <div class="check-row"><span></span>豆包只润色主播能直接说的话</div>
        </section>
        <section class="coach-panel">
          <div class="panel-title">下一步</div>
          <p class="small-copy">把悬浮窗放在主播能看到但不挡直播的位置。主控端继续采集，主播端只看建议。</p>
          <button class="ghost" data-edit-profile>修改组合人设</button>
          <button class="ghost" data-reset>重新建档</button>
        </section>
      </div>
    </div>
  `;
}

function formPage(title, intro, fields, nextText) {
  return `
    <div class="form-card">
      ${setupProgress()}
      <h1>${title}</h1>
      <p>${intro}</p>
      <div class="form-grid">${fields}</div>
      ${actions("上一步", nextText)}
    </div>
  `;
}

function actions(prev, next) {
  return `
    <div class="page-actions">
      <button class="ghost" data-prev>${prev}</button>
      <button class="primary" data-next>${next}</button>
    </div>
  `;
}

function field(label, key, placeholder, extra = "") {
  const help = fieldTips[key] ? `<small class="field-tip">${fieldTips[key]}</small>` : "";
  const sensitive = key === "relay_key";
  return `
    <div class="field ${extra}">
      <label>${label}</label>
      <input type="${sensitive ? "password" : "text"}" ${sensitive ? 'autocomplete="new-password"' : ""} data-bind="${key}" value="${escapeAttr(form[key] || "")}" placeholder="${placeholder}">
      ${help}
    </div>
  `;
}

function area(label, key, placeholder, extra = "") {
  const help = fieldTips[key] ? `<small class="field-tip">${fieldTips[key]}</small>` : "";
  return `
    <div class="field ${extra}">
      <label>${label}</label>
      <textarea data-bind="${key}" placeholder="${placeholder}">${escapeHtml(form[key] || "")}</textarea>
      ${help}
    </div>
  `;
}

function optionGroup(label, key, mode, options, extra = "", customPlaceholder = "") {
  const selected = getSelectedOptions(key, options);
  const custom = getCustomValue(key, options);
  const help = fieldTips[key] ? `<small class="field-tip">${fieldTips[key]}</small>` : "";
  return `
    <div class="field option-field ${extra}" data-option-field="${key}">
      <div class="option-head">
        <label>${label}</label>
        <span>${mode === "single" ? "单选" : "可多选"}</span>
      </div>
      <div class="option-grid">
        ${options.map((item) => `
          <button
            type="button"
            class="choice-chip ${selected.includes(item) ? "selected" : ""}"
            data-option-key="${key}"
            data-option-mode="${mode}"
            data-option-value="${escapeAttr(item)}">
            ${escapeHtml(item)}
          </button>
        `).join("")}
      </div>
      ${customPlaceholder ? `
        <input
          class="custom-option"
          data-custom-bind="${key}"
          value="${escapeAttr(custom)}"
          placeholder="${escapeAttr(customPlaceholder)}">
      ` : ""}
      ${help}
    </div>
  `;
}

function splitOptionValue(value) {
  return String(value || "")
    .split(/\s+\/\s+|\n|，|,|、/)
    .map((item) => item.trim())
    .filter(Boolean);
}

function getSelectedOptions(key, options) {
  const values = splitOptionValue(form[key]);
  return options.filter((option) => values.includes(option));
}

function getCustomValue(key, options) {
  const values = splitOptionValue(form[key]);
  return values.filter((value) => !options.includes(value)).join(" / ");
}

function writeOptionValue(key, values, custom = "") {
  const customParts = splitOptionValue(custom);
  form[key] = [...values, ...customParts].filter(Boolean).join(" / ");
}

function wireInputs() {
  document.querySelectorAll("[data-bind]").forEach((input) => {
    const eventName = input.type === "checkbox" ? "change" : "input";
    input.addEventListener(eventName, () => {
      form[input.dataset.bind] = input.type === "checkbox" ? input.checked : input.value;
    });
  });
  document.querySelectorAll("[data-option-key]").forEach((button) => {
    button.addEventListener("click", () => {
      const key = button.dataset.optionKey;
      const options = optionGroups[key] || [];
      const value = button.dataset.optionValue || "";
      const mode = button.dataset.optionMode || "multi";
      const custom = getCustomValue(key, options);
      let selected = getSelectedOptions(key, options);
      if (mode === "single") {
        selected = selected.includes(value) ? [] : [value];
      } else if (selected.includes(value)) {
        selected = selected.filter((item) => item !== value);
      } else {
        selected = [...selected, value];
        const limit = selectionLimits[key];
        if (limit && selected.length > limit) {
          selected = selected.slice(-limit);
        }
      }
      writeOptionValue(key, selected, custom);
      render();
    });
  });
  document.querySelectorAll("[data-custom-bind]").forEach((input) => {
    input.addEventListener("input", () => {
      const key = input.dataset.customBind;
      writeOptionValue(key, getSelectedOptions(key, optionGroups[key] || []), input.value);
    });
  });
}

function wirePageActions() {
  document.querySelectorAll("[data-next]").forEach((button) => {
    button.addEventListener("click", nextStep);
  });
  document.querySelectorAll("[data-prev]").forEach((button) => {
    button.addEventListener("click", () => {
      activeStep = Math.max(0, activeStep - 1);
      render();
    });
  });
  document.querySelector("[data-preview]")?.addEventListener("click", () => showToast("选择主人设、关系感和表达方式后即可进入测试。"));
  document.querySelector("[data-select-video]")?.addEventListener("click", () => post("selectVideos"));
  document.querySelector("[data-open-overlay]")?.addEventListener("click", () => post("openOverlay", form));
  document.querySelector("[data-refresh]")?.addEventListener("click", () => post("getLatest", form));
  document.querySelector("[data-edit-profile]")?.addEventListener("click", () => {
    activeStep = 1;
    unlockedStep = Math.max(unlockedStep, 1);
    render();
  });
  document.querySelector("[data-reset]")?.addEventListener("click", () => {
    resetLocalProfileForm();
    post("resetAll");
    setupComplete = false;
    activeStep = 1;
    unlockedStep = 1;
    showToast("已重新开始建档");
    render();
  });
  document.querySelector("[data-refresh-smart-rack]")?.addEventListener("click", () => {
    smartRackStatus = { ...smartRackStatus, connection: "正在检测", tone: "checking" };
    render();
    post("getSmartRackStatus");
  });
  document.querySelector("[data-refresh-wechat-collector]")?.addEventListener("click", () => {
    post("getWeChatCollectorStatus");
  });
}

function nextStep() {
  if (!validateStep()) return;
  if (activeStep === 1) {
    showToast("正在保存主播设置…");
    post("saveBaseline", buildSubmissionPayload());
    return;
  }
  unlockedStep = Math.max(unlockedStep, activeStep + 1);
  activeStep = Math.min(5, activeStep + 1);
  render();
}

function validateStep() {
  const required = {
    1: [
      ["main_persona", "请先选择一个核心主人设"],
      ["relationship_feel", "请选择主播与观众的关系感"],
      ["supporting_traits", "请选择主播最常用的情绪外放动作"]
    ]
  };
  const rules = required[activeStep] || [];
  for (const [key, message] of rules) {
    if (key === null && !(state.selected_video_count > 0 || setupComplete)) {
      showToast(message);
      return false;
    }
    if (key && !String(form[key] || "").trim()) {
      showToast(message);
      return false;
    }
  }
  return true;
}

function buildSubmissionPayload() {
  const payload = { ...form };
  payload.anchor_name = String(form.anchor_name || "").trim() || "测试主播";
  payload.platform = String(form.platform || "").trim() || "通用直播平台";
  payload.anchor_type = form.main_persona;
  payload.baseline_goals = form.relationship_feel;
  payload.strengths = limitOptionValue(form.supporting_traits, 1);
  payload.weaknesses = "";
  payload.capability_focus = "";
  payload.taboos = form.disliked_expressions;
  payload.style_preference = "情绪外显";
  payload.suggestion_style = "本地门控提交事实，豆包只选当前一步";
  payload.today_goal = "";
  return payload;
}

function limitOptionValue(value, max) {
  return splitOptionValue(value).slice(0, max).join(" / ");
}

function resetLocalProfileForm() {
  const keepRelay = form.relay_url;
  const keepRoom = form.room;
  const keepKey = form.relay_key;
  Object.assign(form, {
    anchor_id: "",
    anchor_name: "",
    platform: "通用直播平台",
    anchor_type: "",
    main_persona: "",
    supporting_traits: "",
    contrast_persona: "",
    relationship_feel: "",
    interaction_modes: "",
    speaking_rhythms: "",
    disliked_expressions: "",
    anchor_stage: "不参与提醒判断",
    baseline_goals: "",
    strengths: "",
    weaknesses: "",
    taboos: "",
    interaction_preference: "",
    style_preference: "短句直接",
    today_theme: "",
    today_goal: "少打扰高命中",
    today_rhythm: "",
    today_do: "",
    today_avoid: "",
    key_users: "",
    suggestion_style: "本地门控提交事实，豆包只选当前一步",
    relay_url: keepRelay,
    room: keepRoom,
    relay_key: keepKey
  });
  state = { ...state, profile: null, selected_video_count: 0, selected_video_mb: 0 };
  latest = null;
}

function applyProfileState() {
  const profile = state.profile;
  if (!profile?.has_profile) return;
  form.anchor_id = profile.anchor_id || form.anchor_id || "";
  form.anchor_name = profile.anchor_name || form.anchor_name || "";
  const ruleValue = (label) => profile.style_rules?.find((item) => String(item).startsWith(label))?.replace(label, "") || "";
  form.main_persona = normalizeMainPersona(ruleValue("主人设：") || form.main_persona);
  form.anchor_type = form.main_persona;
  form.supporting_traits = normalizeEmotionAction(ruleValue("辅助特质：") || form.supporting_traits);
  form.relationship_feel = normalizeRelationshipFeel(ruleValue("关系感：") || form.relationship_feel);
  form.disliked_expressions = ruleValue("不喜欢的表达：") || form.disliked_expressions;
}

function firstOption(value) {
  return splitOptionValue(value)[0] || "";
}

function normalizeMainPersona(value) {
  const source = firstOption(value);
  if (optionGroups.main_persona.includes(source)) return source;
  if (/外冷内热|甜妹|甜美|甜酷/.test(source)) return "甜酷反差";
  if (/嘴硬|傲娇|护短/.test(source)) return "嘴硬心软";
  if (/高冷|清冷|冷淡|神秘|疏离/.test(source)) return "清冷回撩";
  if (/御姐|掌控|强势/.test(source)) return "御姐掌场";
  if (/元气|活力|热情|阳光/.test(source)) return "元气带动";
  if (/俏皮|有梗|松弛|搞笑|抽象/.test(source)) return "松弛搞笑";
  if (/温柔|陪伴|治愈|共情|知性|故事/.test(source)) return "温柔走心";
  return "直球热烈";
}

function normalizeEmotionAction(value) {
  const source = firstOption(value);
  if (optionGroups.supporting_traits.includes(source)) return source;
  if (/大方|爽朗|带动/.test(source)) return "爽朗带动";
  if (/共情|温暖|体贴|安抚/.test(source)) return "走心被打动";
  if (/俏皮|有梗|搞笑/.test(source)) return "俏皮逗弄";
  return "惊喜放大";
}

function normalizeRelationshipFeel(value) {
  const source = firstOption(value);
  if (optionGroups.relationship_feel.includes(source)) return source;
  if (/姐姐|带队/.test(source)) return "姐姐带队";
  if (/宠粉|偏爱/.test(source)) return "宠粉偏爱";
  if (/若即|距离/.test(source)) return "若即若离";
  return "朋友式亲近";
}

function applyRuntimeConfig() {
  if (runtimeConfigApplied) return;
  const config = state.runtime_config;
  if (!config) return;

  if (config.relay_url && isDefaultRelay(form.relay_url)) {
    form.relay_url = config.relay_url;
  }
  if (config.room && (!form.room || form.room === "demo")) {
    form.room = config.room;
  }
  if (config.relay_key && !form.relay_key) {
    form.relay_key = config.relay_key;
  }
  form.vision_semantic_enabled = state.vision_semantic_enabled === true;
  runtimeConfigApplied = true;
}

function isDefaultRelay(value) {
  const clean = String(value || "").trim().replace(/\/+$/, "");
  return !clean || clean === "http://127.0.0.1:8787" || clean === "http://localhost:8787";
}

function showToast(message) {
  const clean = String(message || "").trim();
  const actionable = /失败|错误|断开|停止|未连接|超时|请|无法|风险/.test(clean);
  if (!clean || !actionable || pageNotice.message === clean) return;
  pageNotice = { message: clean, kind: "warning" };
  if (typeof render === "function") render();
}

function escapeHtml(value) {
  return String(value ?? "")
    .replaceAll("&", "&amp;")
    .replaceAll("<", "&lt;")
    .replaceAll(">", "&gt;")
    .replaceAll('"', "&quot;")
    .replaceAll("'", "&#039;");
}

function escapeAttr(value) {
  return escapeHtml(value).replaceAll("\n", " ");
}

document.querySelectorAll("[data-window]").forEach((button) => {
  button.addEventListener("click", (event) => {
    event.stopPropagation();
    post("windowAction", button.dataset.window);
  });
});

$("#drag-bar").addEventListener("mousedown", (event) => {
  if (event.target.closest("button")) return;
  post("dragWindow");
});

setInterval(() => {
  if (activeStep === 5) post("getLatest", form);
}, 650);

setInterval(() => {
  if (activeStep === 0 || activeStep === 5 || activeStep === 6) post("getSmartRackStatus");
}, 3000);

setInterval(() => {
  if (activeStep === 0 || activeStep === 5 || activeStep === 7) post("getWeChatCollectorStatus");
}, 2000);

render();
post("ready");
if (activeStep === 0 || activeStep === 5 || activeStep === 6) post("getSmartRackStatus");
if (activeStep === 0 || activeStep === 5 || activeStep === 7) post("getWeChatCollectorStatus");
