"use strict";

/**
 * 抖音创作者中心发布（对齐 zhenqianba Electron publishToDouyin，Puppeteer 版）
 */
const fs = require("fs");
const path = require("path");
const os = require("os");
const { delay } = require("./publish_browser.js");
const { adaptCoverForPublish } = require("./publish_cover_adapt.js");

const UPLOAD_URL = "https://creator.douyin.com/creator-micro/content/upload";
const PUBLISH_CONFIRM_TIMEOUT_MS = 15 * 60 * 1000;

const PUBLISH_SUCCESS_TEXTS = [
  "发布成功",
  "已发布",
  "作品发布成功",
  "发布完成",
  "上传成功",
];

const UPLOAD_PAGE_MARKERS = ["/creator-micro/content/upload"];

const VERIFICATION_TEXT_RE =
  /短信验证码|接收短信验证码|请输入验证码|输入验证码|手机验证码|安全验证|验证手机号|获取验证码|发送验证码|发送短信验证码|验证码错误/;

const VERIFICATION_INPUT_SELECTORS = [
  'input[placeholder*="验证码"]',
  'input[placeholder*="输入验证码"]',
  'input[maxlength="6"]',
  'input[maxlength="4"]',
];

const VERIFICATION_ACTION_TEXTS = ["获取验证码", "发送验证码", "验证", "确认"];

function progress(msg) {
  globalThis.__native?.emitProgress?.(msg);
}

function log(msg, fields) {
  globalThis.__native?.log?.("info", msg, fields || null);
}

/** @returns {import('puppeteer').Frame[] | import('puppeteer').Page[]} */
function getSearchFrames(page) {
  try {
    return [page.mainFrame(), ...page.frames()];
  } catch {
    return [page];
  }
}

/** @param {import('puppeteer').Frame | import('puppeteer').Page} frame */
async function isVisibleElement(frame, el) {
  if (!el) return false;
  try {
    return frame.evaluate((node) => {
      const rect = node.getBoundingClientRect();
      if (rect.width < 2 || rect.height < 2) return false;
      const style = window.getComputedStyle(node);
      return (
        style.display !== "none" &&
        style.visibility !== "hidden" &&
        !node.disabled &&
        node.getAttribute("aria-disabled") !== "true"
      );
    }, el);
  } catch {
    return false;
  }
}

/** @param {import('puppeteer').Frame | import('puppeteer').Page} frame */
async function clickElement(frame, el) {
  await frame.evaluate((node) => {
    node.scrollIntoView({ block: "center", inline: "center" });
  }, el);
  await delay(250);
  await el.click();
}

/**
 * @param {import('puppeteer').Page} page
 * @param {string[]} texts
 * @param {{ exact?: boolean }} [opts]
 */
async function clickButtonByTextsInFrames(page, texts, opts = {}) {
  const exact = Boolean(opts.exact);
  for (const frame of getSearchFrames(page)) {
    let handles = [];
    try {
      handles = await frame.$$("button, [role='button']");
    } catch {
      continue;
    }
    for (const btn of handles) {
      let t = "";
      try {
        t = await frame.evaluate((el) => (el.textContent || "").trim(), btn);
      } catch {
        continue;
      }
      const hit = texts.some((w) => (exact ? t === w : t.includes(w)));
      if (!hit) continue;
      if (!(await isVisibleElement(frame, btn))) continue;
      try {
        await clickElement(frame, btn);
        return { ok: true, text: t, frameUrl: frame.url?.() || "" };
      } catch {
        /* try next */
      }
    }
  }
  return { ok: false };
}

/** @param {import('puppeteer').Page} page */
async function fillFirstSelectorInFrames(page, selectors, text) {
  for (const frame of getSearchFrames(page)) {
    for (const sel of selectors) {
      let el = null;
      try {
        if (sel.startsWith("//")) {
          const found = await frame.$x(sel);
          el = found[0] || null;
        } else {
          el = await frame.$(sel);
        }
      } catch {
        continue;
      }
      if (!(await isVisibleElement(frame, el))) continue;
      try {
        await clickElement(frame, el);
        await frame.evaluate((node) => {
          if (node.tagName === "INPUT" || node.tagName === "TEXTAREA") {
            node.value = "";
          } else if (node.isContentEditable) {
            node.textContent = "";
          }
        }, el);
        await el.type(String(text), { delay: 30 });
        return true;
      } catch {
        /* try next */
      }
    }
  }
  return false;
}

/** @param {import('puppeteer').Page} page */
async function ensureLoggedIn(page) {
  const currentUrl = page.url();
  if (currentUrl.includes("login") || currentUrl.includes("passport")) {
    throw new Error("未登录抖音，请先在步骤 07 账号管理中扫码登录");
  }

  let avatar = await page.$("#header-avatar");
  if (!avatar) {
    await delay(5000);
    avatar = await page.$("#header-avatar");
  }
  if (!avatar) {
    if (page.url().includes("login") || page.url().includes("passport")) {
      throw new Error("未登录抖音，请先在步骤 07 账号管理中扫码登录");
    }
    throw new Error("抖音登录已过期，请重新扫码登录");
  }
  log("已检测到 #header-avatar，登录有效");
}

/** @param {import('puppeteer').Page} page */
async function uploadVideo(page, videoPath) {
  progress("正在上传视频…");
  try {
    await page.waitForSelector('input[type="file"]', {
      timeout: 60000,
      visible: false,
    });
  } catch {
    throw new Error("上传页面加载超时，请检查网络或重新登录");
  }

  const fileInput = await page.$('input[type="file"]');
  if (!fileInput) throw new Error("未找到文件上传输入框");

  try {
    await fileInput.uploadFile(videoPath);
    log("视频文件已上传", { videoPath });
  } catch (e) {
    throw new Error(`视频上传失败：${e.message || e}`);
  }
  await delay(3000);
}

/** @param {import('puppeteer').Page} page */
async function waitForVideoProcessing(page) {
  progress("等待视频处理…");

  try {
    await Promise.race([
      page.waitForFunction(
        () => {
          const body = document.body?.innerText || "";
          return body.includes("预览视频") || body.includes("预览");
        },
        { timeout: 60000 },
      ),
      delay(60000),
    ]);
    log("检测到预览相关文案");
  } catch {
    log("未检测到预览元素，继续等待上传完成");
  }

  await delay(2000);

  const maxWait = 300000;
  const start = Date.now();
  let uploadComplete = false;

  while (!uploadComplete && Date.now() - start < maxWait) {
    uploadComplete = await page.evaluate(() => {
      const success = document.querySelector(
        '[data-e2e="upload-success"], .upload-success, [class*="success"]',
      );
      if (success) {
        const r = success.getBoundingClientRect();
        if (r.width > 2 && r.height > 2) return true;
      }
      const titleInput = document.querySelector(
        'textarea[placeholder*="标题"], input[placeholder*="标题"]',
      );
      return Boolean(titleInput && !titleInput.disabled);
    });

    if (uploadComplete) {
      log("视频上传/处理完成");
      break;
    }
    const elapsed = Math.floor((Date.now() - start) / 1000);
    if (elapsed > 0 && elapsed % 10 === 0) {
      progress(`视频处理中… 已等待 ${elapsed} 秒`);
    }
    await delay(2000);
  }

  if (!uploadComplete) {
    log("视频处理等待超时，继续尝试填写表单");
  }
}

/** @param {import('puppeteer').Page} page */
async function uploadCover(page, coverPath) {
  progress("正在上传封面…");
  await delay(3000);

  try {
    const xpaths = [
      '//div[contains(@class, "coverControl-CjlzqC")][.//div[contains(text(), "竖封面")]]',
      '//div[contains(@class, "coverControl")][.//div[contains(text(), "竖封面")]]',
    ];

    let coverControl = null;
    for (const xp of xpaths) {
      const nodes = await page.$x(xp);
      if (nodes[0] && (await isVisibleElement(page, nodes[0]))) {
        coverControl = nodes[0];
        break;
      }
    }

    if (!coverControl) {
      log('未找到封面区域，尝试点击「选择封面」');
      await clickButtonByTextsInFrames(page, ["选择封面"]);
      await delay(3000);
      for (const xp of xpaths) {
        const nodes = await page.$x(xp);
        if (nodes[0] && (await isVisibleElement(page, nodes[0]))) {
          coverControl = nodes[0];
          break;
        }
      }
    }

    if (coverControl) {
      await clickElement(page, coverControl);
      await delay(2000);

      let uploaded = false;
      try {
        await page.waitForSelector(".upload-BvM5FF .semi-upload-hidden-input", {
          timeout: 10000,
          visible: false,
        });
        const semiInput = await page.$(".upload-BvM5FF .semi-upload-hidden-input");
        if (semiInput) {
          await semiInput.uploadFile(coverPath);
          uploaded = true;
          log("封面已通过 semi-upload 上传");
          await delay(3000);
        }
      } catch (e) {
        log(`semi-upload 失败: ${e.message}`);
      }

      if (!uploaded) {
        for (const sel of [
          'input[type="file"][accept*="image/png"]',
          'input[type="file"][accept*="image"]',
        ]) {
          const imageInput = await page.$(sel);
          if (imageInput) {
            await imageInput.uploadFile(coverPath);
            uploaded = true;
            log("封面已通过通用图片输入框上传");
            await delay(3000);
            break;
          }
        }
      }

      const completeXPath = '//*[@id="tooltip-container"]//button[1]';
      const completeNodes = await page.$x(completeXPath);
      if (completeNodes[0] && (await isVisibleElement(page, completeNodes[0]))) {
        await delay(2000);
        await clickElement(page, completeNodes[0]);
        log("已点击封面弹窗「完成」");
      } else {
        await clickButtonByTextsInFrames(page, ["完成"], { exact: true });
      }
      await delay(2000);
      log("封面设置完成");
    } else {
      log("未找到封面控制区域，跳过封面上传");
    }
  } catch (e) {
    log(`封面上传失败（继续）: ${e.message}`);
  }
}

/** @param {import('puppeteer').Page} page */
async function dismissDialogs(page) {
  try {
    await delay(1000);
    await clickButtonByTextsInFrames(page, ["暂不设置", "暂不", "跳过"]);
    await delay(500);
    await page.evaluate(() => {
      for (const el of document.querySelectorAll(
        ".semi-modal-close, [aria-label='close'], [class*='close-icon'], [class*='CloseBtn']",
      )) {
        try {
          el.click();
        } catch {
          /* ignore */
        }
      }
    });
    await delay(500);
  } catch {
    /* ignore */
  }
}

/** @param {import('puppeteer').Page} page */
async function fillTitle(page, title) {
  progress("正在填写标题…");
  const titleText = String(title || "").slice(0, 20);
  const ok = await fillFirstSelectorInFrames(
    page,
    [
      'input[placeholder*="填写作品标题"]',
      'input[placeholder*="标题"]',
      'input[placeholder*="作品"]',
      '//input[contains(@placeholder, "填写作品标题")]',
      'textarea[placeholder*="标题"]',
    ],
    titleText,
  );
  if (ok) log(`标题已填写: "${titleText}"`);
  else log("未找到标题输入框");
  await delay(1000);
}

/** @param {import('puppeteer').Page} page */
async function fillTagsAsDescription(page, tags) {
  const tagList = Array.isArray(tags) ? tags.filter((t) => String(t).trim()) : [];
  if (!tagList.length) return;

  progress("正在填写话题标签…");
  const descSelectors = [
    'textarea[placeholder*="描述"]',
    'textarea[placeholder*="简介"]',
    'textarea[placeholder*="作品"]',
    '[class*="desc"] textarea',
    '[class*="content"] textarea',
    'div[contenteditable="true"]',
  ];

  let target = null;
  let targetFrame = page.mainFrame();

  for (const frame of getSearchFrames(page)) {
    for (const sel of descSelectors) {
      try {
        const el = await frame.$(sel);
        if (await isVisibleElement(frame, el)) {
          target = el;
          targetFrame = frame;
          log(`找到描述输入框: ${sel}`);
          break;
        }
      } catch {
        /* continue */
      }
    }
    if (target) break;
  }

  if (!target) {
    log("未找到描述输入框，跳过话题");
    return;
  }

  try {
    await clickElement(targetFrame, target);
    await delay(300);
    await targetFrame.evaluate((node) => {
      if (node.tagName === "TEXTAREA" || node.tagName === "INPUT") {
        node.value = "";
      } else {
        node.textContent = "";
      }
    }, target);
    await delay(300);

    for (let i = 0; i < tagList.length; i++) {
      const tag = String(tagList[i]).trim();
      const clean = tag.startsWith("#") ? tag : `#${tag}`;
      log(`输入话题 ${i + 1}/${tagList.length}: ${clean}`);
      await page.keyboard.type(clean, { delay: 50 });
      await delay(300);
      await page.keyboard.press("Space");
      await delay(500);

      const hasPopup = await page.evaluate(() => {
        for (const n of document.querySelectorAll(
          '[class*="topic"], [class*="hashtag"]',
        )) {
          const r = n.getBoundingClientRect();
          if (r.width > 2 && r.height > 2) return true;
        }
        return false;
      });
      if (hasPopup) {
        await page.keyboard.press("Enter");
        await delay(300);
      }
    }
    log("所有话题已输入完成");
  } catch (e) {
    log(`填写话题失败: ${e.message}`);
  }
  await delay(1000);
}

/** @param {import('puppeteer').Page} page */
async function detectVerificationInFrames(page) {
  for (const frame of getSearchFrames(page)) {
    try {
      const hit = await frame.evaluate(
        (reSource, inputSels, actionTexts) => {
          const re = new RegExp(reSource);
          const bodyText = document.body?.innerText || "";
          const hasText = re.test(bodyText);
          const inputs = [];
          for (const sel of inputSels) {
            inputs.push(...document.querySelectorAll(sel));
          }
          const visibleInputs = inputs.filter((el) => {
            const r = el.getBoundingClientRect();
            return r.width > 2 && r.height > 2;
          });
          const actionBtns = [...document.querySelectorAll("button")].filter(
            (btn) => {
              const t = (btn.textContent || "").trim();
              return actionTexts.includes(t);
            },
          );
          return {
            detected:
              (hasText && visibleInputs.length > 0) ||
              (visibleInputs.length > 0 && actionBtns.length > 0),
          };
        },
        VERIFICATION_TEXT_RE.source,
        VERIFICATION_INPUT_SELECTORS,
        VERIFICATION_ACTION_TEXTS,
      );
      if (hit.detected) return { detected: true, frameUrl: frame.url?.() || "" };
    } catch {
      /* next frame */
    }
  }
  return { detected: false };
}

/** @param {import('puppeteer').Page} page */
async function detectPublishSuccessInFrames(page) {
  for (const frame of getSearchFrames(page)) {
    try {
      const textHit = await frame.evaluate((texts) => {
        const body = document.body?.innerText || "";
        return texts.some((t) => body.includes(t));
      }, PUBLISH_SUCCESS_TEXTS);
      if (textHit) {
        return { detected: true, reason: "success_text" };
      }
    } catch {
      /* continue */
    }
  }

  const currentUrl = page.url();
  const leftUpload =
    UPLOAD_PAGE_MARKERS.length > 0 &&
    !UPLOAD_PAGE_MARKERS.some((m) => currentUrl.includes(m));
  if (
    leftUpload &&
    !currentUrl.includes("login") &&
    !currentUrl.includes("verify") &&
    !currentUrl.includes("passport")
  ) {
    return { detected: true, reason: `url_changed:${currentUrl}` };
  }
  return { detected: false };
}

/**
 * 对齐 Electron waitForPublishConfirmation（15 分钟）
 * @param {import('puppeteer').Page} page
 */
async function waitForPublishConfirmation(page) {
  const start = Date.now();
  let verificationDetected = false;

  while (Date.now() - start < PUBLISH_CONFIRM_TIMEOUT_MS) {
    if (page.isClosed()) {
      return {
        success: false,
        status: verificationDetected ? "verification_required" : "failed",
        message: verificationDetected
          ? "发布页面已关闭，验证码流程未完成"
          : "发布页面已关闭",
        videoUrl: "",
        videoId: "",
        keepPageOpen: false,
      };
    }

    const verification = await detectVerificationInFrames(page);
    if (verification.detected) {
      if (!verificationDetected) {
        verificationDetected = true;
        progress("检测到验证码，请在浏览器中完成验证");
        log("进入验证码等待", { frameUrl: verification.frameUrl });
      }
    }

    const success = await detectPublishSuccessInFrames(page);
    if (success.detected) {
      log("检测到发布成功", { reason: success.reason });
      return {
        success: true,
        status: "success",
        message: "发布成功",
        videoUrl: page.url(),
        videoId: "",
        keepPageOpen: false,
      };
    }

    await delay(1500);
  }

  if (verificationDetected) {
    return {
      success: false,
      status: "verification_required",
      message: "检测到短信验证码，请在浏览器中完成验证码输入并确认发布",
      videoUrl: page.url(),
      videoId: "",
      requiresVerification: true,
      keepPageOpen: true,
    };
  }

  return {
    success: false,
    status: "manual_pending",
    message:
      "已点击发布按钮，但未检测到明确的发布成功标识，请在浏览器中确认是否仍需验证码或手动确认",
    videoUrl: page.url(),
    videoId: "",
    keepPageOpen: true,
  };
}

/**
 * 对齐 Electron 发布按钮多选择器策略（跨 frame）
 * @param {import('puppeteer').Page} page
 */
async function clickPublishButtonElectron(page) {
  const cssBuckets = [
    "button.primary",
    'button[class*="primary"]',
    'button[class*="button-"]',
    "#popover-tip-container button",
    "button",
  ];

  for (const frame of getSearchFrames(page)) {
    for (const bucket of cssBuckets) {
      let handles = [];
      try {
        handles = await frame.$$(bucket);
      } catch {
        continue;
      }
      for (const btn of handles) {
        let trimmedText = "";
        try {
          trimmedText = await frame.evaluate(
            (el) => (el.textContent || "").trim(),
            btn,
          );
        } catch {
          continue;
        }
        log(`检查发布按钮: bucket=${bucket}, text="${trimmedText}"`);
        if (trimmedText !== "发布" && trimmedText !== "确定发布") continue;
        if (!(await isVisibleElement(frame, btn))) continue;
        try {
          await clickElement(frame, btn);
          log(`已点击发布按钮: text="${trimmedText}"`);
          return { clicked: true, text: trimmedText };
        } catch (e) {
          log(`点击发布按钮失败: ${e.message}`);
        }
      }
    }
  }
  return { clicked: false };
}

/** 部分账号点击「发布」后会弹出二次确认 */
async function clickSecondaryPublishConfirm(page) {
  await delay(1500);
  for (const want of ["确定发布", "确认发布", "发布"]) {
    const r = await clickButtonByTextsInFrames(page, [want], {
      exact: want === "发布",
    });
    if (r.ok) {
      log(`已点击二次确认: ${r.text}`);
      return true;
    }
  }
  return false;
}

function manualPending(message, page) {
  return {
    success: false,
    status: "manual_pending",
    message,
    videoUrl: page.url(),
    videoId: "",
    keepPageOpen: true,
  };
}

/**
 * @param {import('puppeteer').Page} page
 * @param {object} params
 */
async function publishToDouyin(page, params) {
  const {
    videoPath,
    coverPath,
    title,
    tags = [],
    autoPublish,
    description,
  } = params;

  if (!fs.existsSync(videoPath)) throw new Error("视频文件不存在");
  if (!fs.existsSync(coverPath)) throw new Error("封面文件不存在");

  page.setDefaultTimeout(60000);
  page.setDefaultNavigationTimeout(60000);

  let adaptedCoverPath = coverPath;
  try {
    progress("正在适配封面尺寸…");
    adaptedCoverPath = adaptCoverForPublish(coverPath, "douyin");
    if (adaptedCoverPath !== coverPath) {
      log("封面已适配为抖音竖版 JPG", { adaptedCoverPath });
    }
  } catch (e) {
    log(`封面适配跳过: ${e.message}`);
  }

  try {
    log("开始发布到抖音");
    progress("正在打开抖音上传页…");
    await page.goto(UPLOAD_URL, { waitUntil: "domcontentloaded" });
    await delay(3000);
    log(`当前 URL: ${page.url()}`);

    await ensureLoggedIn(page);

    if (!page.url().includes("content/upload")) {
      await page.goto(UPLOAD_URL, { waitUntil: "domcontentloaded" });
      await delay(3000);
    }

    await uploadVideo(page, videoPath);
    await waitForVideoProcessing(page);
    await uploadCover(page, adaptedCoverPath);
    await dismissDialogs(page);
    await fillTitle(page, title);

    const tagSource =
      Array.isArray(tags) && tags.length
        ? tags
        : String(description || "")
            .split(/[,，\s#]+/)
            .map((t) => t.trim())
            .filter(Boolean);
    await fillTagsAsDescription(page, tagSource);

    progress("表单填写完成");
    log("=== 所有准备工作已完成 ===");

    if (!autoPublish) {
      progress("请在浏览器中检查并手动点击发布（窗口将保持打开）");
      await delay(30000);
      return manualPending(
        "准备工作已完成，请在浏览器中手动点击发布按钮",
        page,
      );
    }

    progress("正在查找并点击发布按钮…");
    await delay(2000);

    let publishTriggered = false;
    const clickResult = await clickPublishButtonElectron(page);
    if (clickResult.clicked) {
      publishTriggered = true;
      await clickSecondaryPublishConfirm(page);
      try {
        await Promise.race([
          page.waitForFunction(
            () => {
              const body = document.body?.innerText || "";
              return body.includes("发布成功") || body.includes("上传成功");
            },
            { timeout: 10000 },
          ),
          delay(5000),
        ]);
        log("发布操作已提交");
      } catch {
        log("未检测到即时成功提示，进入发布确认等待");
      }
    } else {
      log("未找到发布按钮，请手动点击");
    }

    if (publishTriggered) {
      progress("等待发布结果（最长 15 分钟）…");
      return waitForPublishConfirmation(page);
    }

    return manualPending(
      "未找到明确的发布按钮，请在浏览器中手动确认发布",
      page,
    );
  } catch (err) {
    try {
      const shotDir =
        process.env.AICLIENT_DATA_DIR ||
        path.join(os.homedir(), ".aiclient");
      const shotPath = path.join(shotDir, "douyin_publish_error.png");
      await page.screenshot({ path: shotPath, fullPage: false });
      log(`错误截图已保存: ${shotPath}`);
    } catch {
      /* ignore */
    }
    throw err;
  }
}

module.exports = { publishToDouyin };
