/* ============================================================
 * 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";
var PANEL_VERSION = "3.4.0";
(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",
  configVersion: -1,
  configTimer: null,
  updateInfo: null,
  updateRequested: false,
};

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);
}

function compareVersion(a, b) {
  var aa = String(a || "0").split("."), bb = String(b || "0").split(".");
  var n = Math.max(aa.length, bb.length);
  for (var i = 0; i < n; i++) {
    var av = parseInt(aa[i] || "0", 10) || 0, bv = parseInt(bb[i] || "0", 10) || 0;
    if (av > bv) return 1;
    if (av < bv) return -1;
  }
  return 0;
}

/* ---------------- 本地存储 ---------------- */
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, UXP_SHELL = null;
if (IS_UXP) {
  PS = require("photoshop");
  var uxpMod = require("uxp");
  UXP_STORAGE = uxpMod.storage.localFileSystem;
  UXP_FMT = uxpMod.storage.formats;
  UXP_SHELL = uxpMod.shell;
}

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");
  },

  /* ---- 当前选区 bounds；无选区返回 null ---- */
  selectionBounds: function () {
    if (IS_UXP) return UxpBridge.selectionBounds();
    return CepBridge.selectionBounds();
  },

  /* ---- 本地文件多选 → [{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, fmt, bounds);
    return CepBridge.insert(b64, fmt, bounds);
  },

  /* ---- 启动安装器：先下载，全部 Photoshop 退出后覆盖 ---- */
  launchUpdater: function (version) {
    var uri = "huicraft-update://install?version=" + encodeURIComponent(version || "");
    if (IS_UXP && UXP_SHELL && UXP_SHELL.openExternal) {
      return UXP_SHELL.openExternal(uri, "启动 AI绘匠更新器；更新将在 Photoshop 退出后自动安装。").then(function (r) {
        if (r) throw new Error(String(r));
        return true;
      });
    }
    return new Promise(function (resolve, reject) {
      try {
        if (!window.cep || !window.cep.util || !window.cep.util.openURLInDefaultBrowser) {
          reject(new Error("未找到本机更新器")); return;
        }
        var result = window.cep.util.openURLInDefaultBrowser(uri);
        if (result && Number(result.err || 0) !== 0) reject(new Error("启动更新器失败"));
        else resolve(true);
      } catch (e) { reject(e); }
    });
  },

  /* ---- 缩略图（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 = {
  num: function (v) {
    if (v == null) return NaN;
    if (typeof v === "number") return v;
    if (typeof v._value === "number") return v._value;
    if (typeof v.value === "number") return v.value;
    try { if (v.as) return v.as("px"); } catch (e) {}
    return Number(v);
  },

  cleanBounds: function (b) {
    if (!b) return null;
    var out = {
      left: UxpBridge.num(b.left), top: UxpBridge.num(b.top),
      right: UxpBridge.num(b.right), bottom: UxpBridge.num(b.bottom),
    };
    if (!isFinite(out.left) || !isFinite(out.top) || !isFinite(out.right) ||
        !isFinite(out.bottom) || out.right <= out.left || out.bottom <= out.top) return null;
    return out;
  },

  selectionBounds: function () {
    var doc = PS.app.activeDocument;
    if (!doc) return Promise.resolve(null);
    try {
      var direct = UxpBridge.cleanBounds(doc.selection && doc.selection.bounds);
      if (direct) return Promise.resolve(direct);
    } catch (e) {}
    return PS.action.batchPlay([{
      _obj: "get",
      _target: [{ _property: "selection" }, { _ref: "document", _id: doc._id || doc.id }],
      _options: { dialogOptions: "dontDisplay" },
    }], {}).then(function (items) {
      var d = items && items[0] ? (items[0].selection || items[0]) : null;
      return UxpBridge.cleanBounds(d);
    }).catch(function () { return null; });
  },

  capture: function (mode) {
    if (!PS.app.activeDocument) return Promise.reject(new Error("请先打开一个文档"));
    return PS.core.executeAsModal(function () {
      var doc = PS.app.activeDocument;
      var layer = doc.activeLayers && doc.activeLayers[0];
      var boundsPromise = mode === "selection" ? UxpBridge.selectionBounds() : Promise.resolve(null);
      return boundsPromise.then(function (bounds) {
        if (mode === "selection" && !bounds) throw new Error("请先用选框工具选择区域");
        if (mode !== "selection" && !layer) throw new Error("请选中一个图层");
        var opts = {
          documentID: doc._id || doc.id,
          colorSpace: "RGB",
          componentSize: 8,
          applyAlpha: true,
        };
        if (mode === "selection") opts.sourceBounds = bounds;
        else {
          opts.layerID = layer._id || layer.id;
          var layerBounds = UxpBridge.cleanBounds(layer.boundsNoEffects || layer.bounds);
          if (layerBounds) opts.sourceBounds = layerBounds;
        }
        return PS.imaging.getPixels(opts).then(function (result) {
          if (!result || !result.imageData) throw new Error("Photoshop 未返回图像像素");
          return PS.imaging.encodeImageData({ imageData: result.imageData, base64: true })
            .then(function (enc) {
              var w = result.imageData.width, h = result.imageData.height;
              try { result.imageData.dispose(); } catch (e) {}
              return {
                b64: (enc && enc.base64 !== undefined) ? enc.base64 : enc,
                format: "jpeg", bounds: bounds || null, width: w, height: h,
              };
            });
        });
      });
    }, { commandName: mode === "selection" ? "AI绘匠读取选区" : "AI绘匠读取图层" })
      .catch(function (e) { throw new Error(e.message || "抓取失败"); });
  },

  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) {
          var n = String(en.name || "").toLowerCase();
          var fmt = /\.jpe?g$/.test(n) ? "jpeg" : /\.webp$/.test(n) ? "webp" : "png";
          return { b64: bytesToB64(new Uint8Array(buf)), format: fmt };
        });
      });
      return Promise.all(ps);
    });
  },

  insert: function (b64, fmt, bounds) {
    var bytes = b64ToBytes(b64);
    var ext = fmt === "jpeg" || fmt === "jpg" ? "jpg" : fmt === "webp" ? "webp" : "png";
    return UXP_STORAGE.getTemporaryFolder().then(function (folder) {
      return folder.createFile("huijiang-result-" + Date.now() + "." + ext, { overwrite: true });
    }).then(function (file) {
      return file.write(bytes.buffer, { format: UXP_FMT.binary }).then(function () { return file; });
    }).then(function (file) {
      return PS.core.executeAsModal(function () {
        if (!PS.app.activeDocument) return PS.app.open(file);
        var token = UXP_STORAGE.createSessionToken(file);
        return PS.action.batchPlay([{
          _obj: "placeEvent",
          null: { _path: token, _kind: "local" },
          freeTransformCenterState: { _enum: "quadCenterState", _value: "QCSAverage" },
          _options: { dialogOptions: "dontDisplay" },
        }], {}).then(function () { return UxpBridge.fitPlacedLayer(bounds); });
      }, { commandName: "AI绘匠插入生成图" });
    });
  },

  fitPlacedLayer: function (bounds) {
    var doc = PS.app.activeDocument, layer = doc.activeLayers[0];
    try { layer.name = "AI绘匠 " + new Date().toLocaleTimeString(); } catch (e) {}
    if (!bounds) return Promise.resolve(null);
    var b = UxpBridge.cleanBounds(layer.boundsNoEffects || layer.bounds);
    if (!b) return Promise.reject(new Error("无法读取生成图尺寸"));
    var w = b.right - b.left, h = b.bottom - b.top;
    var tw = bounds.right - bounds.left, th = bounds.bottom - bounds.top;
    var scale = Math.max(tw / w, th / h) * 100;
    return layer.resize(scale, scale).then(function () {
      var nb = UxpBridge.cleanBounds(layer.boundsNoEffects || layer.bounds);
      var dx = (bounds.left + bounds.right - nb.left - nb.right) / 2;
      var dy = (bounds.top + bounds.bottom - nb.top - nb.bottom) / 2;
      return layer.translate(dx, dy);
    }).then(function () {
      return PS.action.batchPlay([{
        _obj: "set",
        _target: [{ _ref: "channel", _property: "selection" }],
        to: { _obj: "rectangle",
          top: { _unit: "pixelsUnit", _value: bounds.top },
          left: { _unit: "pixelsUnit", _value: bounds.left },
          bottom: { _unit: "pixelsUnit", _value: bounds.bottom },
          right: { _unit: "pixelsUnit", _value: bounds.right } },
        _options: { dialogOptions: "dontDisplay" },
      }, {
        _obj: "make", new: { _class: "channel" },
        at: { _ref: "channel", _enum: "channel", _value: "mask" },
        using: { _enum: "userMaskEnabled", _value: "revealSelection" },
        _options: { dialogOptions: "dontDisplay" },
      }], {});
    });
  },
};

/* ============================================================
 * 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 (raw) {
      if (!raw || raw.indexOf("ERR:") === 0) {
        throw new Error((raw || "抓取失败").replace(/^ERR:/, ""));
      }
      var info = null;
      try { info = JSON.parse(raw); } catch (e) { info = { path: raw }; }
      var readResult = null;
      try {
        var encoding = window.cep && window.cep.encoding
          ? window.cep.encoding.Base64 : "Base64";
        readResult = window.cep.fs.readFile(info.path, encoding);
      } finally {
        try { window.cep.fs.unlink(info.path); } catch (e2) {}
      }
      var data = readResult && readResult.data;
      if (!readResult || Number(readResult.err || 0) !== 0 || !data) {
        var code = readResult && readResult.err !== undefined ? "（错误码 " + readResult.err + "）" : "";
        throw new Error("读取截图失败" + code);
      }
      var bounds = info.bounds && info.bounds.length === 4
        ? { left: info.bounds[0], top: info.bounds[1], right: info.bounds[2], bottom: info.bounds[3] }
        : null;
      return { b64: data, format: "png", bounds: bounds,
        width: Number(info.width || 0), height: Number(info.height || 0) };
    });
  },

  selectionBounds: function () {
    return this.eval("hjSelectionBounds()").then(function (raw) {
      if (!raw || raw.indexOf("ERR:") === 0) return null;
      try {
        var b = JSON.parse(raw);
        return b && b.length === 4 ? { left:b[0], top:b[1], right:b[2], bottom:b[3] } : null;
      } catch (e) { return null; }
    });
  },

  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, fmt, bounds) {
    // 按实际格式写到 CEP 可写目录，再交给 ExtendScript place
    var base = "";
    try {
      base = (window.__adobe_cep__.getSystemPath("userData") || "").replace(/\\/g, "/");
    } catch (e) {}
    if (!base) base = "C:/Users/Public";
    var ext = fmt === "jpeg" || fmt === "jpg" ? "jpg" : fmt === "webp" ? "webp" : "png";
    var fp = base + "/hj_ins_" + Date.now() + "." + ext;
    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 b64ToBytes(text) {
  var clean = String(text || "").replace(/[^A-Za-z0-9+/=]/g, "");
  var table = {}, i, out = [], buffer = 0, bits = 0;
  for (i = 0; i < B64C.length; i++) table[B64C.charAt(i)] = i;
  for (i = 0; i < clean.length; i++) {
    var ch = clean.charAt(i);
    if (ch === "=") break;
    if (table[ch] === undefined) continue;
    buffer = (buffer << 6) | table[ch]; bits += 6;
    if (bits >= 8) { bits -= 8; out.push((buffer >> bits) & 255); }
  }
  return new Uint8Array(out);
}

/* ============================================================
 * 登录 / 配置
 * ============================================================ */
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, client_version: PANEL_VERSION }).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;
  if (d.tools) S.tools = d.tools.filter(function (t) { return t.enabled !== false; });
  if (d.config_version !== undefined) S.configVersion = Number(d.config_version);
  if (d.update) S.updateInfo = d.update;
  if (!S.toolId && S.tools.length) S.toolId = S.tools[0].id;
  renderAll();
}

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

function checkRemoteConfig() {
  if (!S.key) return;
  httpPost("/api/panel/config", {
    key: S.key, config_version: S.configVersion, client_version: PANEL_VERSION,
  }).then(function (d) {
    if (!d.ok) return;
    if (d.changed) applyConfig(d);
    else {
      if (d.config_version !== undefined) S.configVersion = Number(d.config_version);
      if (d.update) { S.updateInfo = d.update; renderUpdateStatus(); }
    }
  });
}

function startConfigTimer() {
  if (S.configTimer) clearInterval(S.configTimer);
  S.configTimer = setInterval(checkRemoteConfig, 300000);
}

function logout() {
  LS.del("hj_key");
  S.key = "";
  if (S.configTimer) { clearInterval(S.configTimer); S.configTimer = null; }
  $("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();
  startConfigTimer();
}

/* ============================================================
 * 主界面渲染
 * ============================================================ */
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();
  renderFoot();
  renderUpdateStatus();
}

function renderUpdateStatus() {
  var btn = $("update-status");
  if (!btn) return;
  var latest = S.updateInfo && S.updateInfo.latest_version;
  var available = latest && compareVersion(latest, PANEL_VERSION) > 0;
  btn.classList.toggle("has-update", !!available && !S.updateRequested);
  btn.classList.toggle("updating", !!S.updateRequested);
  btn.disabled = !available || S.updateRequested;
  if (S.updateRequested) {
    btn.textContent = "退出PS后安装";
    btn.title = "更新正在后台下载；关闭全部 Photoshop 后会自动覆盖旧版本";
  } else if (available) {
    btn.textContent = "有更新 v" + latest;
    btn.title = "发现 v" + latest + "，点击下载并更新";
  } else {
    btn.textContent = "v" + PANEL_VERSION.replace(/\.0$/, "");
    btn.title = "当前已是最新版 v" + PANEL_VERSION;
  }
}

function requestUpdate() {
  var latest = S.updateInfo && S.updateInfo.latest_version;
  if (!latest || compareVersion(latest, PANEL_VERSION) <= 0 || S.updateRequested) return;
  S.updateRequested = true;
  renderUpdateStatus();
  toast("正在启动更新器…", 3000);
  Platform.launchUpdater(latest).then(function () {
    toast("更新正在后台下载；关闭全部 Photoshop 后会自动覆盖旧版本。", 6000);
  }).catch(function (e) {
    S.updateRequested = false;
    renderUpdateStatus();
    toast("启动更新失败，请重新运行最新版安装器：" + (e.message || e), 5000);
  });
}

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 ? "修图需求" : "生图需求";
  $("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 selectedRatio() {
  return $("p-ratio").value || "auto";
}

/* 智能比例在有选区时匹配最接近的服务端档位，减少贴回后的裁切。 */
function ratioForBounds(bounds) {
  var selected = selectedRatio();
  if (selected !== "auto" || !bounds) return selected;
  var w = bounds.right - bounds.left, h = bounds.bottom - bounds.top;
  if (w <= 0 || h <= 0) return selected;
  var target = w / h;
  var choices = [
    { name:"21:9", value:21/9 }, { name:"16:9", value:16/9 },
    { name:"3:2", value:3/2 }, { name:"4:3", value:4/3 },
    { name:"1:1", value:1 }, { name:"3:4", value:3/4 },
    { name:"2:3", value:2/3 }, { name:"9:16", value:9/16 },
    { name:"9:21", value:9/21 },
  ];
  var best = choices[0], score = Math.abs(Math.log(target / best.value));
  for (var i = 1; i < choices.length; i++) {
    var next = Math.abs(Math.log(target / choices[i].value));
    if (next < score) { best = choices[i]; score = next; }
  }
  return best.name;
}

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; }

  S.busy = true;
  var hintedBounds = null;
  for (var n = 0; n < S.images.length; n++) {
    if (S.images[n].bounds) { hintedBounds = S.images[n].bounds; break; }
  }
  var wantsSelection = $("sw-stick").getAttribute("data-on") === "1";
  if (hintedBounds || !wantsSelection) submitGenerate(t, prompt, hintedBounds);
  else Platform.selectionBounds().then(function (bounds) {
    submitGenerate(t, prompt, bounds || null);
  }).catch(function () { submitGenerate(t, prompt, null); });
}

function submitGenerate(t, prompt, bounds) {
  var params = {
    prompt: prompt,
    ratio: ratioForBounds(bounds),
    count: parseInt($("p-count").value, 10) || 1,
    resolution: $("p-resolution").value || "1K",
    quality: $("p-quality").value || "auto",
    stick_selection: !!bounds,
  };
  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, autoInsert: !!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);
  });

  if (bounds && S._gen && S._gen.autoInsert) autoInsertResults(images, bounds);

  // 历史（缩略图异步补）
  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 autoInsertResults(images, bounds) {
  var chain = Promise.resolve();
  images.forEach(function (im) {
    chain = chain.then(function () { return Platform.insertImage(im.b64, im.format, bounds); });
  });
  toast("生成完成，正在自动插入选区…");
  chain.then(function () {
    toast("已自动插入选区，共 " + images.length + " 个新图层 ✓");
  }).catch(function (e) {
    toast("自动插入失败，可点击结果重试：" + (e.message || e), 4200);
  });
}

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();
  $("update-status").onclick = requestUpdate;
  var saved = LS.get("hj_key", "");
  if (saved) {
    $("key-input").value = saved;
    S.key = saved;
    httpPost("/api/panel/login", { key: saved, client_version: PANEL_VERSION }).then(function (d) {
      if (d.ok) { applyConfig(d); enterMain(); }
      else { toast(d.msg || "密钥已失效，请重新登录"); }
    });
  }
});
