/* ============================================================
 * AI绘匠 v3 - Photoshop 面板（UXP 与 CEP 共用）
 * 架构: 面板 --HTTPS--> 云端(guanli.aichajian.qiyefu.top) --代理--> 中转站
 * 密钥登录；打开 PS 即用，关 PS 即退。
 * 兼容性: ES5（CEP6/Chromium41 可跑），不用箭头函数/let/const/解构。
 * ============================================================ */
"use strict";

/* ---------------- 常量与状态 ---------------- */
var SERVER = "https://guanli.aichajian.qiyefu.top";
(function () {                       // ?server= 覆盖（测试用）
  var m = /(?:\?|&)_?server=([^&]+)/.exec(location.search || "");
  if (m) { try { SERVER = decodeURIComponent(m[1]); } catch (e) {} }
})();

var S = {
  key: "",            // 登录密钥
  keyInfo: null,      // {note, quota_total, quota_used, remaining, expires_at}
  tools: [],          // 云端工具
  toolId: "",         // 当前工具 id
  images: [],         // 参考图 [{b64, format, bounds}]
  taskId: null,
  pollTimer: null,
  busy: false,
  view: "main",
};

var RATIOS = [
  { v: "auto", label: "智能" },
  { v: "21:9", label: "21:9", w: 16, h: 7 },
  { v: "16:9", label: "16:9", w: 16, h: 9 },
  { v: "3:2",  label: "3:2",  w: 15, h: 10 },
  { v: "4:3",  label: "4:3",  w: 13, h: 10 },
  { v: "1:1",  label: "1:1",  w: 11, h: 11 },
  { v: "3:4",  label: "3:4",  w: 10, h: 13 },
  { v: "2:3",  label: "2:3",  w: 9,  h: 13 },
  { v: "9:16", label: "9:16", w: 7,  h: 12 },
];

var $ = function (id) { return document.getElementById(id); };
function esc(s) {
  return String(s == null ? "" : s).replace(/&/g, "&amp;")
    .replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}

function toast(msg, ms) {
  var t = $("toast");
  t.textContent = msg;
  t.classList.add("show");
  clearTimeout(t._tm);
  t._tm = setTimeout(function () { t.classList.remove("show"); }, ms || 2600);
}

/* ---------------- 本地存储 ---------------- */
var LS = {
  get: function (k, dv) {
    try { var v = localStorage.getItem(k); return v === null ? dv : v; }
    catch (e) { return dv; }
  },
  set: function (k, v) { try { localStorage.setItem(k, v); } catch (e) {} },
  del: function (k) { try { localStorage.removeItem(k); } catch (e) {} },
};

/* ---------------- HTTP（UXP: fetch / CEP: XHR） ---------------- */
function httpPost(path, body) {
  var url = SERVER + path;
  var payload = JSON.stringify(body || {});
  if (typeof fetch === "function" && !window.__adobe_cep__) {
    return fetch(url, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: payload,
    }).then(function (r) {
      return r.json().catch(function () { return { ok: false, msg: "响应异常 HTTP " + r.status }; });
    });
  }
  return new Promise(function (resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.open("POST", url, true);
    xhr.setRequestHeader("Content-Type", "application/json");
    xhr.onload = function () {
      try { resolve(JSON.parse(xhr.responseText)); }
      catch (e) { resolve({ ok: false, msg: "响应异常 HTTP " + xhr.status }); }
    };
    xhr.onerror = function () { resolve({ ok: false, msg: "网络错误，无法连接服务器" }); };
    xhr.send(payload);
  });
}

/* ============================================================
 * 平台适配层
 * ============================================================ */
var IS_UXP = (typeof require === "function") && !window.__adobe_cep__;
var PS = null, UXP_STORAGE = null, UXP_FMT = null;
if (IS_UXP) {
  PS = require("photoshop");
  var uxpMod = require("uxp");
  UXP_STORAGE = uxpMod.storage.localFileSystem;
  UXP_FMT = uxpMod.storage.formats;
}

var Platform = {

  /* ---- 从当前图层抓图 → {b64, format} ---- */
  captureLayer: function () {
    if (IS_UXP) return UxpBridge.capture("layer");
    return CepBridge.capture("layer");
  },

  /* ---- 从选区抓图 → {b64, format, bounds:[l,t,r,b]} ---- */
  captureSelection: function () {
    if (IS_UXP) return UxpBridge.capture("selection");
    return CepBridge.capture("selection");
  },

  /* ---- 本地文件多选 → [{b64, format}] ---- */
  pickLocal: function () {
    if (IS_UXP) return UxpBridge.pickLocal();
    return CepBridge.pickLocal();
  },

  /* ---- 插入生成图（bounds 存在时缩放贴合到选区） ---- */
  insertImage: function (b64, fmt, bounds) {
    if (IS_UXP) return UxpBridge.insert(b64, bounds);
    return CepBridge.insert(b64, bounds);
  },

  /* ---- 缩略图（canvas 可用时，异步回调） ---- */
  makeThumb: function (b64, fmt, cb) {
    try {
      var img = new Image();
      img.onload = function () {
        var out = null;
        try {
          var c = document.createElement("canvas");
          var w = 60, h = Math.max(1, Math.round(60 * img.height / img.width));
          c.width = w; c.height = h;
          var ctx = c.getContext("2d");
          ctx.drawImage(img, 0, 0, w, h);
          out = c.toDataURL("image/jpeg", 0.72);
        } catch (e) { out = null; }
        cb(out);
      };
      img.onerror = function () { cb(null); };
      img.src = "data:image/" + (fmt || "png") + ";base64," + b64;
    } catch (e) { cb(null); }
  },
};

/* ============================================================
 * UXP 桥
 * ============================================================ */
var UxpBridge = {
  capture: function (mode) {
    return new Promise(function (resolve, reject) {
      if (!PS.app.activeDocument) { reject(new Error("请先打开一个文档")); return; }
      var out = null;
      PS.core.executeAsModal(function () {
        try {
          var doc = PS.app.activeDocument;
          var layer = doc.activeLayers && doc.activeLayers[0];
          var opts = {};
          var bounds = null;
          if (mode === "selection") {
            var b = UxpBridge.selBounds(doc);
            if (!b) throw new Error("请先用选框工具选择区域");
            bounds = b;
            opts.sourceBounds = b;
          } else {
            if (!layer) throw new Error("请选中一个图层");
            opts.sourceLayer = layer;
          }
          var result;
          try {
            result = PS.imaging.getPixels(opts);
          } catch (e) {
            if (mode === "selection") { result = PS.imaging.getPixels({}); }
            else { throw e; }
          }
          return PS.imaging.encodeImageData({
            imageData: result.imageData, base64: true, format: "png",
          }).then(function (enc) {
            out = {
              b64: (enc && enc.base64 !== undefined) ? enc.base64 : enc,
              format: "png",
              bounds: bounds || null,
              width: result.imageData && result.imageData.width,
              height: result.imageData && result.imageData.height,
            };
          });
        } catch (e) {
          out = { __err: e.message || String(e) };
        }
      }).then(function () {
        if (out && out.__err) reject(new Error(out.__err));
        else if (out) resolve(out);
        else reject(new Error("抓取像素失败"));
      }).catch(function (e) { reject(new Error(e.message || "抓取失败")); });
    });
  },

  selBounds: function (doc) {
    try {
      var b = doc.selection.bounds;
      return { left: b.left, top: b.top, right: b.right, bottom: b.bottom };
    } catch (e) { return null; }
  },

  pickLocal: function () {
    return UXP_STORAGE.getFileForOpening({
      allowMultiple: true,
      types: ["png", "jpg", "jpeg", "webp"],
    }).then(function (entries) {
      if (!entries) return [];
      if (!Array.isArray(entries)) entries = [entries];
      var ps = entries.slice(0, 5).map(function (en) {
        return en.read({ format: UXP_FMT.binary }).then(function (buf) {
          return { b64: bytesToB64(new Uint8Array(buf)), format: "png" };
        });
      });
      return Promise.all(ps);
    });
  },

  insert: function (b64, bounds) {
    return PS.core.executeAsModal(function () {
      return PS.imaging.decodeImageData({ base64: b64 }).then(function (dec) {
        var data = dec.imageData || dec;
        var w = dec.width, h = dec.height;
        var doc = PS.app.activeDocument;
        if (!doc) {
          return PS.action.batchPlay([{
            _obj: "make",
            new: {
              _obj: "document",
              width: { _unit: "pixelsUnit", _value: Math.max(w, bounds ? (bounds.right - bounds.left) : 0) },
              height: { _unit: "pixelsUnit", _value: Math.max(h, bounds ? (bounds.bottom - bounds.top) : 0) },
              resolution: { _unit: "densityUnit", _value: 72 },
              mode: { _class: "RGBMode" },
              fill: { _enum: "fillContents", _value: "white" },
            },
          }], {}).then(function () {
            return UxpBridge.putAndFit(data, w, h, bounds);
          });
        }
        return UxpBridge.putAndFit(data, w, h, bounds);
      });
    });
  },

  putAndFit: function (data, w, h, bounds) {
    return PS.action.batchPlay([{ _obj: "make", new: { _obj: "layer" } }], {})
      .then(function () {
        var doc = PS.app.activeDocument;
        var layer = doc.activeLayers[0];
        try { layer.name = "AI绘匠 " + new Date().toLocaleTimeString(); } catch (e) {}
        return PS.imaging.putPixels({
          imageData: data,
          targetLayer: layer._id,
          sourceBounds: { left: 0, top: 0, right: w, bottom: h },
        });
      })
      .then(function () {
        if (!bounds) return null;
        // 贴回选区：等比缩放至选区内并居中
        var doc = PS.app.activeDocument;
        var layer = doc.activeLayers[0];
        var tw = bounds.right - bounds.left, th = bounds.bottom - bounds.top;
        var scale = Math.min(tw / w, th / h) * 100;
        return layer.resize(scale, scale).then(function () {
          return layer.bounds;
        }).then(function (b) {
          var cx = (b.left.as ? b.left.as("px") : b.left);
          var cy = (b.top.as ? b.top.as("px") : b.top);
          var rx = (b.right.as ? b.right.as("px") : b.right);
          var ry = (b.bottom.as ? b.bottom.as("px") : b.bottom);
          var dx = (bounds.left + bounds.right) / 2 - (cx + rx) / 2;
          var dy = (bounds.top + bounds.bottom) / 2 - (cy + ry) / 2;
          if (Math.abs(dx) > 0.5 || Math.abs(dy) > 0.5) return layer.translate(dx, dy);
          return null;
        });
      });
  },
};

/* ============================================================
 * CEP 桥（ExtendScript evalScript）
 * ============================================================ */
var CepBridge = {
  eval: function (code) {
    return new Promise(function (resolve) {
      try {
        window.__adobe_cep__.evalScript(code, function (r) {
          resolve(r);
        });
      } catch (e) { resolve("ERR:" + e); }
    });
  },

  capture: function (mode) {
    var self = this;
    return this.eval("hjCapture('" + mode + "')").then(function (path) {
      if (!path || path.indexOf("ERR:") === 0) {
        throw new Error((path || "抓取失败").replace(/^ERR:/, ""));
      }
      return self.eval("hjSelectionBounds()").then(function (sb) {
        var bounds = null;
        if (sb && sb.indexOf("ERR:") !== 0 && mode === "selection") {
          try { bounds = JSON.parse(sb); } catch (e) { bounds = null; }
          if (bounds && bounds.length === 4) {
            bounds = { left: bounds[0], top: bounds[1], right: bounds[2], bottom: bounds[3] };
          } else bounds = null;
        }
        var data = window.cep.fs.readFile(path, "base64").data;
        if (!data) throw new Error("读取截图失败");
        return { b64: data, format: "png", bounds: bounds };
      });
    });
  },

  pickLocal: function () {
    return new Promise(function (resolve) {
      var inp = document.createElement("input");
      inp.type = "file";
      inp.accept = "image/png,image/jpeg,image/webp";
      inp.multiple = true;
      inp.style.display = "none";
      inp.onchange = function () {
        var files = Array.prototype.slice.call(inp.files || [], 0, 5);
        if (!files.length) { resolve([]); return; }
        var out = [], left = files.length;
        files.forEach(function (f) {
          var rd = new FileReader();
          rd.onload = function () {
            var s = String(rd.result || "");
            var i = s.indexOf("base64,");
            var fmt = "png";
            if (s.indexOf("image/jpeg") >= 0) fmt = "jpeg";
            else if (s.indexOf("image/webp") >= 0) fmt = "webp";
            out.push({ b64: i >= 0 ? s.slice(i + 7) : s, format: fmt });
          };
          rd.onloadend = function () { if (--left === 0) resolve(out); };
          rd.onerror = function () { if (--left === 0) resolve(out); };
          rd.readAsDataURL(f);
        });
        document.body.removeChild(inp);
      };
      document.body.appendChild(inp);
      inp.click();
    });
  },

  insert: function (b64, bounds) {
    // 写临时 PNG 到 CEP 可写目录，再交给 ExtendScript place
    var base = "";
    try {
      base = (window.__adobe_cep__.getSystemPath("userData") || "").replace(/\\/g, "/");
    } catch (e) {}
    if (!base) base = "C:/Users/Public";
    var fp = base + "/hj_ins_" + Date.now() + ".png";
    var w = window.cep.fs.writeFile(fp, b64, "base64");
    if (w.err !== 0) return Promise.reject(new Error("写入临时文件失败"));
    var code = bounds
      ? "hjInsert('" + fp + "', '" + JSON.stringify(bounds) + "')"
      : "hjInsert('" + fp + "')";
    return this.eval(code).then(function (r) {
      if (r && r.indexOf("ERR:") === 0) throw new Error(r.replace(/^ERR:/, ""));
      try { window.cep.fs.unlink(fp); } catch (e) {}
      return r;
    });
  },
};

/* ArrayBuffer → base64（UXP 本地文件用） */
var B64C = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
function bytesToB64(bytes) {
  var out = [], i;
  for (i = 0; i < bytes.length; i += 3) {
    var b1 = bytes[i], b2 = i + 1 < bytes.length ? bytes[i + 1] : 0,
        b3 = i + 2 < bytes.length ? bytes[i + 2] : 0;
    out.push(B64C[b1 >> 2]);
    out.push(B64C[((b1 & 3) << 4) | (b2 >> 4)]);
    out.push(i + 1 < bytes.length ? B64C[((b2 & 15) << 2) | (b3 >> 6)] : "=");
    out.push(i + 2 < bytes.length ? B64C[b3 & 63] : "=");
  }
  return out.join("");
}

/* ============================================================
 * 登录 / 配置
 * ============================================================ */
function doLogin() {
  var key = $("key-input").value.trim();
  if (!key) { toast("请输入密钥"); return; }
  $("btn-login").disabled = true;
  $("btn-login").textContent = "登录中…";
  httpPost("/api/panel/login", { key: key }).then(function (d) {
    $("btn-login").disabled = false;
    $("btn-login").textContent = "登 录";
    if (!d.ok) { toast(d.msg || "登录失败"); return; }
    S.key = key;
    LS.set("hj_key", key);
    applyConfig(d);
    enterMain();
  });
}

function applyConfig(d) {
  S.keyInfo = d.key || S.keyInfo;
  S.tools = (d.tools || []).filter(function (t) { return t.enabled !== false; });
  if (!S.toolId && S.tools.length) S.toolId = S.tools[0].id;
  renderAll();
}

function refreshConfig() {           // 生成后刷新额度
  return httpPost("/api/panel/login", { key: S.key }).then(function (d) {
    if (d.ok) applyConfig(d);
  });
}

function logout() {
  LS.del("hj_key");
  S.key = "";
  $("main-view").classList.add("hidden");
  $("login-view").classList.remove("hidden");
  $("key-input").value = "";
}

function enterMain() {
  $("login-view").classList.add("hidden");
  $("main-view").classList.remove("hidden");
  switchView("main");
  renderHistory();
}

/* ============================================================
 * 主界面渲染
 * ============================================================ */
function currentTool() {
  for (var i = 0; i < S.tools.length; i++)
    if (S.tools[i].id === S.toolId) return S.tools[i];
  return S.tools[0] || null;
}

function renderAll() {
  renderHead();
  renderToolSwitch();
  renderUpload();
  renderModelOptions();
  renderFoot();
}

function quotaText() {
  if (!S.keyInfo) return "-";
  if (!S.keyInfo.quota_total) return "不限额度";
  return "剩余 " + (S.keyInfo.quota_total - S.keyInfo.quota_used) + " 张";
}

function renderHead() {
  $("uhead-name").textContent = "普通用户";
  $("uhead-quota").textContent = quotaText();
  $("acc-note").textContent = "已登录";
  $("acc-quota").textContent = S.keyInfo
    ? (S.keyInfo.quota_total ? (S.keyInfo.quota_used + " / " + S.keyInfo.quota_total + " 张")
       : "不限额度") : "-";
  $("acc-expires").textContent = S.keyInfo
    ? (S.keyInfo.expires_at ? new Date(S.keyInfo.expires_at * 1000).toLocaleDateString() : "永久")
    : "-";
}

function renderToolSwitch() {
  var box = $("tool-switch");
  box.innerHTML = "";
  S.tools.forEach(function (t) {
    var b = document.createElement("button");
    b.className = "tool-tab" + (t.id === S.toolId ? " on" : "");
    var ico = { brush: "🖌", star: "✦", text: "T", image: "▦", magic: "✨", hd: "◑", expand: "⤢", cube: "◧" }[t.icon] || "✦";
    b.innerHTML = '<span class="t-ico">' + ico + "</span><span>" + esc(t.name) + "</span>";
    b.onclick = function () {
      if (!box.classList.contains("tool-menu-open")) { box.classList.add("tool-menu-open"); return; }
      S.toolId = t.id;
      box.classList.remove("tool-menu-open");
      renderAll();
    };
    box.appendChild(b);
  });
}

function toolNeedsImages() {
  var t = currentTool();
  return t && t.need_image && t.need_image !== "none" && t.need_image !== "optional";
}

function renderUpload() {
  var t = currentTool();
  var banana = t && t.id === "banana_retouch";
  $("prompt-title").textContent = banana ? "修图需求" : "生图需求";
  $("model-label").textContent = banana ? "修图模型" : "生图模型";
  $("row-quality").classList.toggle("hidden", banana);
  $("prompt-input").placeholder = banana
    ? "请输入您的修图需求，例如：把背景换成雪山日落"
    : "描述想要生成的画面；也可以添加参考图";

  var row = $("img-row");
  row.innerHTML = "";
  S.images.forEach(function (im, idx) {
    var cell = document.createElement("div");
    cell.className = "img-cell" + (im.bounds ? " stick" : "");
    cell.innerHTML = '<img src="data:image/' + (im.format || "png") + ";base64," + im.b64 + '">'
      + '<span class="del" title="移除">✕</span>';
    cell.onclick = function (ev) {
      if (ev.target && ev.target.className === "del") return;
      openImgPop(im.b64, im.format);
    };
    cell.querySelector(".del").onclick = function (ev) {
      ev.stopPropagation();
      S.images.splice(idx, 1);
      renderUpload();
    };
    row.appendChild(cell);
  });
  if (S.images.length < 5) {
    var add = document.createElement("button");
    add.className = "img-add";
    add.textContent = "＋";
    add.title = "添加原图";
    add.onclick = openUploadMenu;
    row.appendChild(add);
  }
}

function renderModelOptions() {
  var t = currentTool();
  var sel = $("p-model");
  var models = (t && t.api && t.api.models) || [];
  sel.innerHTML = "";
  if (!models.length) {
    var o = document.createElement("option");
    o.value = ""; o.textContent = "默认";
    sel.appendChild(o);
  }
  models.forEach(function (m, i) {
    var o = document.createElement("option");
    o.value = m;
    o.textContent = m;
    if (i === 0) o.selected = true;
    sel.appendChild(o);
  });
}

function selectedRatio() {
  return $("p-ratio").value || "auto";
}

function renderFoot() {
  $("foot-quota").textContent = quotaText()
    + " · 本次 " + (parseInt($("p-count").value, 10) || 1) + " 张";
}

/* ============================================================
 * 上传：三种方式
 * ============================================================ */
function closePops() {
  $("pop-mask").classList.add("hidden");
  $("menu-pop").classList.add("hidden");
  $("img-pop").classList.add("hidden");
  $("prompt-pop").classList.add("hidden");
}

function openUploadMenu() {
  $("menu-pop").classList.remove("hidden");
  $("pop-mask").classList.remove("hidden");
}

function addImage(im) {
  if (S.images.length >= 5) { toast("最多 5 张参考图"); return false; }
  S.images.push(im);
  if (!im.width || !im.height) {
    try {
      var probe = new Image();
      probe.onload = function () { im.width = probe.naturalWidth || probe.width; im.height = probe.naturalHeight || probe.height; };
      probe.src = "data:image/" + (im.format || "png") + ";base64," + im.b64;
    } catch (e) {}
  }
  renderUpload();
  return true;
}

function addDroppedFiles(files) {
  var list = Array.prototype.slice.call(files || [], 0, Math.max(0, 5 - S.images.length));
  if (!list.length) return;
  var added = 0;
  list.forEach(function (file) {
    if (!/^image\//i.test(file.type || "") && !/\.(png|jpe?g|webp)$/i.test(file.name || "")) return;
    var rd = new FileReader();
    rd.onload = function () {
      var value = String(rd.result || ""), comma = value.indexOf(",");
      var fmt = /image\/jpe?g/i.test(value) ? "jpeg" : /image\/webp/i.test(value) ? "webp" : "png";
      if (addImage({ b64: comma >= 0 ? value.slice(comma + 1) : value, format: fmt })) {
        added++;
        toast("已添加 " + added + " 张参考图");
      }
    };
    rd.readAsDataURL(file);
  });
}

function onUploadPick(src) {
  closePops();
  if (src === "local") {
    Platform.pickLocal().then(function (list) {
      var added = 0;
      (list || []).forEach(function (im) {
        if (!im || !im.b64) return;
        if (addImage({ b64: im.b64, format: im.format })) added++;
      });
      if (added) toast("已添加 " + added + " 张");
    }).catch(function (e) { toast(e.message || "添加失败"); });
  } else if (src === "layer") {
    toast("正在抓取当前图层…");
    Platform.captureLayer().then(function (im) {
      addImage(im);
      toast("已从图层获取 ✓");
    }).catch(function (e) { toast(e.message || "抓取失败"); });
  } else if (src === "selection") {
    toast("正在抓取选区…");
    Platform.captureSelection().then(function (im) {
      im.bounds = im.bounds || null;
      addImage(im);
      if (im.bounds) setSwitch("sw-stick", true);   // 有选区时自动开贴回
      toast("已从选区获取 ✓");
    }).catch(function (e) { toast(e.message || "抓取失败"); });
  }
}

function setSwitch(id, on) {
  var el = $(id);
  el.classList.toggle("on", !!on);
  el.setAttribute("data-on", on ? "1" : "0");
}

/* ============================================================
 * 生成
 * ============================================================ */
function onGenerate() {
  if (S.busy) return;
  var t = currentTool();
  if (!t) { toast("暂无可用工具"); return; }
  var prompt = $("prompt-input").value.trim();
  if (!prompt) { toast("请输入提示词"); return; }
  var needImg = toolNeedsImages();
  if (needImg && !S.images.length) { toast("请先添加至少 1 张原图"); return; }

  var params = {
    prompt: prompt,
    ratio: selectedRatio(),
    count: parseInt($("p-count").value, 10) || 1,
    model: $("p-model").value || "",
    resolution: $("p-resolution").value || "1K",
    quality: $("p-quality").value || "auto",
    stick_selection: $("sw-stick").getAttribute("data-on") === "1",
  };
  // 贴回选区：找带 bounds 的参考图
  var bounds = null;
  if (params.stick_selection) {
    for (var i = 0; i < S.images.length; i++)
      if (S.images[i].bounds) { bounds = S.images[i].bounds; break; }
  }

  S.busy = true;
  var btn = $("btn-gen");
  btn.disabled = true; btn.textContent = "提交中…";
  $("result-empty").classList.add("hidden");
  $("result-grid").innerHTML = "";
  $("progress-box").classList.remove("hidden");
  setProgress(4, "正在提交服务器…");

  var body = { key: S.key, tool_id: t.id, params: params };
  if (S.images.length) body.images = S.images.map(function (im) {
    return { b64: im.b64, format: im.format || "png", width: im.width || 0, height: im.height || 0 };
  });

  httpPost("/api/panel/generate", body).then(function (d) {
    if (!d.ok) {
      failGenerate(d.msg || "提交失败");
      return;
    }
    S.taskId = d.task_id;
    S._gen = { tool: t, prompt: prompt, bounds: bounds, t0: Date.now() };
    btn.textContent = "生成中…";
    pollTask();
  }).catch(function () { failGenerate("网络错误"); });
}

function setProgress(pct, stage) {
  $("progress-fill").style.width = Math.max(2, Math.min(100, pct)) + "%";
  if (stage) $("progress-stage").textContent = stage;
}

function failGenerate(msg) {
  S.busy = false;
  S.taskId = null;
  clearTimeout(S.pollTimer);
  var btn = $("btn-gen");
  btn.disabled = false; btn.textContent = "立即生成";
  $("progress-box").classList.add("hidden");
  $("result-empty").classList.remove("hidden");
  toast(msg, 3600);
  refreshConfig();
}

function pollTask() {
  if (!S.taskId) return;
  // 假进度（2s 一跳，最高 93%）
  var el = (Date.now() - (S._gen ? S._gen.t0 : Date.now())) / 1000;
  setProgress(Math.min(93, 5 + el * 1.2), "生成中… 请稍候");
  httpPost("/api/panel/task/" + S.taskId, { key: S.key }).then(function (d) {
    if (!S.taskId) return;
    if (!d.ok) { failGenerate(d.msg || "查询失败"); return; }
    var st = d.status;
    if (st === "done") { onTaskDone(d); return; }
    if (st === "error") { failGenerate(d.error || "生成失败"); return; }
    if (st === "canceled") { failGenerate("已取消"); return; }
    S.pollTimer = setTimeout(pollTask, 2200);
  }).catch(function () {
    S.pollTimer = setTimeout(pollTask, 3000);
  });
}

function onTaskDone(d) {
  var taskId = S.taskId;
  S.busy = false;
  S.taskId = null;
  clearTimeout(S.pollTimer);
  setProgress(100, "生成完成 ✓");
  var btn = $("btn-gen");
  btn.disabled = false; btn.textContent = "立即生成";

  var images = d.images || [];
  $("result-count").textContent = images.length ? ("共 " + images.length + " 张") : "";
  var bounds = S._gen ? S._gen.bounds : null;
  setTimeout(function () { $("progress-box").classList.add("hidden"); }, 600);

  var grid = $("result-grid");
  grid.innerHTML = "";
  if (!images.length) {
    $("result-empty").classList.remove("hidden");
    toast("生成完成（无返回图）");
    return;
  }
  images.forEach(function (im) {
    var cell = document.createElement("div");
    cell.className = "res-cell";
    cell.innerHTML = '<img src="data:image/' + (im.format || "png") + ";base64," + im.b64 + '">'
      + '<button class="ins">插入图层</button>';
    cell.onclick = function () { openImgPop(im.b64, im.format, bounds); };
    cell.querySelector(".ins").onclick = function (ev) {
      ev.stopPropagation();
      insertToPS(im, bounds);
    };
    grid.appendChild(cell);
  });

  // 历史（缩略图异步补）
  pushHistory({
    t: Date.now(),
    tool: S._gen ? S._gen.tool.name : "",
    prompt: S._gen ? S._gen.prompt : "",
    thumb: "",
    task_id: taskId || "",
  });
  Platform.makeThumb(images[0].b64, images[0].format, function (thumb) {
    if (!thumb) return;
    var list = [];
    try { list = JSON.parse(LS.get(histKey(), "[]")) || []; } catch (e) { list = []; }
    for (var i = 0; i < list.length; i++) {
      if (list[i].task_id === taskId) { list[i].thumb = thumb; break; }
    }
    LS.set(histKey(), JSON.stringify(list));
    renderHistory();
  });
  refreshConfig();
  renderFoot();
}

function insertToPS(im, bounds) {
  toast("正在插入图层…");
  Platform.insertImage(im.b64, im.format, bounds || null).then(function () {
    toast("已插入为新图层 ✓");
  }).catch(function (e) {
    toast("插入失败: " + (e.message || e), 3600);
  });
}

function openImgPop(b64, fmt, bounds) {
  $("img-pop-img").src = "data:image/" + (fmt || "png") + ";base64," + b64;
  $("img-pop").classList.remove("hidden");
  $("pop-mask").classList.remove("hidden");
  $("img-pop")._img = { b64: b64, format: fmt };
  $("img-pop")._bounds = bounds || null;
}

/* ============================================================
 * 历史（本地）
 * ============================================================ */
function histKey() { return "hj_hist3_" + (S.key || "").slice(0, 14); }

function pushHistory(h) {
  var list = [];
  try { list = JSON.parse(LS.get(histKey(), "[]")) || []; } catch (e) { list = []; }
  list.unshift(h);
  if (list.length > 30) list = list.slice(0, 30);
  LS.set(histKey(), JSON.stringify(list));
  renderHistory();
}

function renderHistory() {
  var box = $("history-list");
  if (!box) return;
  var list = [];
  try { list = JSON.parse(LS.get(histKey(), "[]")) || []; } catch (e) { list = []; }
  box.innerHTML = "";
  if (!list.length) {
    box.innerHTML = '<div class="hist-empty">暂无历史记录</div>';
    return;
  }
  list.forEach(function (h, idx) {
    var it = document.createElement("div");
    it.className = "hist-item";
    var d = new Date(h.t || 0);
    var mm = (d.getMonth() + 1) + "-" + d.getDate() + " " +
      ("0" + d.getHours()).slice(-2) + ":" + ("0" + d.getMinutes()).slice(-2);
    it.innerHTML = (h.thumb
        ? '<img src="' + h.thumb + '">'
        : '<img style="opacity:.25" src="data:image/png;base64,">')
      + '<div class="h-info"><div class="h-tool">' + esc(h.tool) + " · " + mm + "</div>"
      + '<div class="h-prompt">' + esc(h.prompt) + "</div></div>";
    it.onclick = function () {
      if (!h.task_id) { toast("该记录无图可取"); return; }
      toast("取回中…");
      httpPost("/api/panel/task/" + h.task_id, { key: S.key }).then(function (d) {
        if (d.ok && d.status === "done" && d.images && d.images.length) {
          insertToPS(d.images[0], null);
        } else {
          toast("该记录的图片已被服务器清理");
        }
      });
    };
    box.appendChild(it);
  });
}

/* ============================================================
 * 视图切换
 * ============================================================ */
function switchView(v) {
  S.view = v;
  ["main", "history", "account"].forEach(function (n) {
    $("view-" + n).classList.toggle("on", n === v);
  });
  ["main", "history", "account"].forEach(function (n) {
    var b = $("nav-" + n);
    if (b) b.classList.toggle("on", n === v);
  });
}

/* ============================================================
 * 绑定
 * ============================================================ */
function bind() {
  $("btn-login").onclick = doLogin;
  $("key-input").onkeydown = function (e) {
    if (e.key === "Enter" || e.keyCode === 13) doLogin();
  };

  $("nav-main").onclick = function () { switchView("main"); };
  $("nav-history").onclick = function () { switchView("history"); };
  $("nav-account").onclick = function () { switchView("account"); };
  $("btn-logout").onclick = logout;

  // 上传菜单
  var items = $("menu-pop").querySelectorAll(".pop-item");
  for (var i = 0; i < items.length; i++) {
    items[i].onclick = function () { onUploadPick(this.getAttribute("data-src")); };
  }
  $("pop-mask").onclick = closePops;

  // 提示词
  $("prompt-input").oninput = function () { $("prompt-count").textContent = this.value.length + " / 3000"; };
  $("btn-clear").onclick = function () { $("prompt-input").value = ""; $("prompt-input").oninput(); };
  $("btn-prompt-full").onclick = function () {
    $("prompt-pop-input").value = $("prompt-input").value;
    $("prompt-pop").classList.remove("hidden"); $("pop-mask").classList.remove("hidden");
  };
  $("btn-prompt-close").onclick = closePops;
  $("btn-prompt-save").onclick = function () {
    $("prompt-input").value = $("prompt-pop-input").value;
    $("prompt-input").oninput(); closePops();
  };

  // 文件夹/桌面拖图
  var drop = $("img-drop");
  drop.ondragover = function (e) { e.preventDefault(); drop.classList.add("drag"); };
  drop.ondragleave = function () { drop.classList.remove("drag"); };
  drop.ondrop = function (e) {
    e.preventDefault(); drop.classList.remove("drag");
    addDroppedFiles(e.dataTransfer && e.dataTransfer.files);
  };

  // 开关
  $("sw-stick").onclick = function () {
    setSwitch("sw-stick", this.getAttribute("data-on") !== "1");
  };
  $("p-count").onchange = renderFoot;

  // 生成
  $("btn-gen").onclick = onGenerate;
  $("btn-cancel").onclick = function () {
    if (S.taskId) {
      httpPost("/api/panel/cancel", { key: S.key, task_id: S.taskId });
      failGenerate("正在取消…");
    }
  };

  // 历史清空
  $("btn-hist-clear").onclick = function () {
    LS.del(histKey());
    renderHistory();
    toast("已清空");
  };

  // 大图弹层
  $("btn-pop-close").onclick = closePops;
  $("btn-pop-insert").onclick = function () {
    var im = $("img-pop")._img, b = $("img-pop")._bounds;
    closePops();
    if (im) insertToPS(im, b);
  };
}

/* ============================================================
 * 启动
 * ============================================================ */
document.addEventListener("DOMContentLoaded", function () {
  bind();
  var saved = LS.get("hj_key", "");
  if (saved) {
    $("key-input").value = saved;
    S.key = saved;
    httpPost("/api/panel/login", { key: saved }).then(function (d) {
      if (d.ok) { applyConfig(d); enterMain(); }
      else { toast(d.msg || "密钥已失效，请重新登录"); }
    });
  }
});
