<template>
  <div class="manage-info-page">
    <h1 class="page-title">经营信息</h1>

    <el-form
      ref="formRef"
      class="manage-form"
      :model="form"
      :rules="rules"
      label-width="200px"
      require-asterisk-position="right"
      label-position="right"
    >
      <!-- 基础经营信息 -->
      <section class="form-block">
        <h2 class="section-head">
          <span class="section-bar" aria-hidden="true" />
          经营信息
        </h2>

        <el-form-item prop="merchantShortName">
          <template #label>
            <span class="label-with-icon">
              商户简称
              <el-tooltip placement="top" content="简称将展示给消费者，需与营业执照或品牌一致，勿使用无关或违规词汇。">
                <el-icon class="info-icon" :size="16"><InfoFilled /></el-icon>
              </el-tooltip>
            </span>
          </template>
          <div class="field-stack">
            <el-input
              v-model="form.merchantShortName"
              placeholder="请输入商户简称"
              clearable
              maxlength="64"
              show-word-limit
              style="max-width: 480px"
            />
            <p class="field-tip">
              1. 在支付完成页向买家展示，需与微信经营类目相关。<br />
              2.简称要求：<br />
              不支持单纯以人名来命名，若为个体户经营，可用“个体户+经营者名称”或“经营者名称+业务”命名，如个体户“张三”或“张三餐饮店”;<br />
              不支持无实际意义的文案，如“XX”特约商户，“800”，“XX客服电话XXX”;
            </p>
          </div>
        </el-form-item>

        <el-form-item prop="servicePhone">
          <template #label>
            <span class="label-with-icon">
              客服电话
              <el-tooltip placement="top" content="用于用户咨询与平台联系，请填写真实可接通号码。">
                <el-icon class="info-icon" :size="16"><InfoFilled /></el-icon>
              </el-tooltip>
            </span>
          </template>
          <div class="field-stack">
            <el-input
              v-model="form.servicePhone"
              placeholder="请输入客服电话"
              clearable
              maxlength="20"
              style="max-width: 480px"
            />
            <p class="field-tip">
              1.请填写真实、可接通的客服电话，以便用户咨询。<br />
              2.请确保电话畅通,以便入驻平台回拨确认。<br />
            </p>
          </div>
        </el-form-item>

        <el-form-item label="经营场景" prop="scenarioBits" required>
          <div class="field-stack">
            <div class="scenario-checks">
              <el-checkbox v-model="form.scenarios.offline"> 线下场景 </el-checkbox>
              <el-checkbox v-model="form.scenarios.miniProgram"> 小程序 </el-checkbox>
              <el-checkbox v-model="form.scenarios.app"> APP </el-checkbox>
              <el-checkbox v-model="form.scenarios.wechatAccount"> 服务号或订阅号 </el-checkbox>
              <el-checkbox v-model="form.scenarios.website"> 互联网网站 </el-checkbox>
              <el-checkbox v-model="form.scenarios.wecom"> 企业微信 </el-checkbox>
            </div>
            <p class="field-tip">
              请勾选实际售卖商品/提供服务场景(至少一项),以便为你开通需要的支付权限<br />
              建议只勾选目前必须的尝尽，以便尽快通过入驻审核，其他支付权限你可在入驻后再根据实际需要发起申请
            </p>
          </div>
        </el-form-item>
      </section>

      <!-- 线下场景 -->
      <section v-if="form.scenarios.offline" class="form-block sub-section">
        <h2 class="section-head">
          <span class="section-bar" aria-hidden="true" />
          线下场所
          <span class="section-intro"
            >你选择了“线下场所”场景，入驻成功后，服务商可帮商户发起付款码支付，JSAPI支付，同时系统将会对地址等信息进行核实</span
          >
        </h2>

        <el-form-item label="线下场所名称" prop="offlineVenueName">
          <el-input v-model="form.offlineVenueName" placeholder="请输入线下场所名称" clearable style="max-width: 480px" />
        </el-form-item>

        <el-form-item label="线下场所省市" prop="offlineProvinceCity">
          <el-select v-model="form.offlineProvince" placeholder="请选择省" filterable style="width: 200px">
            <el-option v-for="p in provinceOptions" :key="p.value" :label="p.label" :value="p.value" />
          </el-select>
          <el-select
            v-model="form.offlineCity"
            placeholder="请选择市"
            filterable
            style="width: 200px; margin-left: 12px"
            :disabled="!form.offlineProvince"
          >
            <el-option v-for="c in cityOptions" :key="c.value" :label="c.label" :value="c.value" />
          </el-select>
        </el-form-item>

        <el-form-item label="线下场所地址" prop="offlineAddress">
          <div class="field-stack">
            <el-input
              v-model="form.offlineAddress"
              type="textarea"
              :rows="2"
              placeholder="请输入详细地址"
              maxlength="200"
              show-word-limit
              style="max-width: 480px"
            />
            <p class="field-tip">如有多个经营场所，请填写主要营业地址。</p>
          </div>
        </el-form-item>

        <el-form-item prop="offlineStorefrontUrls">
          <template #label>
            <span class="label-with-icon">
              线下场所门头照片
              <el-tooltip placement="top" content="需清晰展示门头招牌与店名。">
                <el-icon class="info-icon" :size="16"><InfoFilled /></el-icon>
              </el-tooltip>
            </span>
          </template>
          <div class="field-stack">
            <el-upload
              v-model:file-list="form.offlineStorefrontFileList"
              list-type="picture-card"
              :limit="5"
              accept=".jpg,.jpeg,.png,.bmp,image/jpeg,image/png,image/bmp"
              :before-upload="beforeImageUpload"
              :http-request="opt => handleMultiUpload(opt, 'storefront')"
              :on-remove="(f, fl) => onMultiRemove(f, fl, 'storefront')"
            >
              <el-icon><Plus /></el-icon>
            </el-upload>
            <p class="field-tip">
              1.场景图片正面拍摄且清晰，完整，图片不得有遮挡;<br />
              2.门店招牌清晰，招牌名称，文字可辨识，门框完整，且店面显示在营，若为停车场等无固定门头照片的经营场所，可上传岗亭/出入闸口。
            </p>
          </div>
        </el-form-item>

        <el-form-item prop="offlineInteriorUrls">
          <template #label>
            <span class="label-with-icon">
              线下场所内部照片
              <el-tooltip placement="top" content="展示店内经营环境。">
                <el-icon class="info-icon" :size="16"><InfoFilled /></el-icon>
              </el-tooltip>
            </span>
          </template>
          <div class="field-stack">
            <el-upload
              v-model:file-list="form.offlineInteriorFileList"
              list-type="picture-card"
              :limit="5"
              accept=".jpg,.jpeg,.png,.bmp,image/jpeg,image/png,image/bmp"
              :before-upload="beforeImageUpload"
              :http-request="opt => handleMultiUpload(opt, 'interior')"
              :on-remove="(f, fl) => onMultiRemove(f, fl, 'interior')"
            >
              <el-icon><Plus /></el-icon>
            </el-upload>
            <p class="field-tip">
              1.场景图片正面拍摄且清晰，完整，图片不得有遮挡;<br />
              2.门店招牌清晰，招牌名称，文字可辨识，门框完整，且店面显示在营，若为停车场等无固定门头照片的经营场所，可上传岗亭/出入闸口。
            </p>
          </div>
        </el-form-item>

        <el-form-item label="线下场所对应的服务号或公众号 APPID">
          <div class="field-stack">
            <el-input v-model="form.offlineMpAppId" placeholder="选填" clearable style="max-width: 480px" />
            <p class="field-tip">
              1.可填写已认领的服务号或公众号(需时已认证的服务号，政府或媒体类型的公众号)，小程序，应用的APPID.<br />
              2.完成进件后，系统发起特约商户号与该APPID的绑定(即配置为sub_appid,可在发起支付时传入)<br />
              (1)若APPID主体与商家主体一致，则直接完成绑定；<br />
              (2)若APPID主体与商家主体不一致，则需商户提供主体变更证明材料，审核通过后完成绑定。
            </p>
          </div>
        </el-form-item>
      </section>

      <!-- 小程序 -->
      <section v-if="form.scenarios.miniProgram" class="form-block sub-section">
        <h2 class="section-head">
          <span class="section-bar" aria-hidden="true" />
          小程序<span class="section-intro">你选择了“小程序”场景，入驻成功后，服务商可帮商户发起JSAPI支付</span>
        </h2>

        <el-form-item label="小程序 APPID" prop="miniProgramAppId">
          <div class="field-stack">
            <el-input v-model="form.miniProgramAppId" placeholder="请输入小程序 APPID" clearable style="max-width: 480px" />
            <p class="field-tip">
              1.请填写已认证小程序的 APPID。<br />
              2.完成进件后，系统发起特约商户号与该APPID的绑定(即配置为sub_appid,可在发起支付时传入)<br />
              (1)若APPID主体与商家主体一致，则直接完成绑定；<br />
              (2)若APPID主体与商家主体不一致，则需商户提供主体变更证明材料，审核通过后完成绑定。
            </p>
          </div>
        </el-form-item>

        <el-form-item label="小程序截图">
          <div class="field-stack">
            <el-upload
              v-model:file-list="form.miniProgramShotFileList"
              list-type="picture-card"
              :limit="5"
              accept=".jpg,.jpeg,.png,.bmp,image/jpeg,image/png,image/bmp"
              :before-upload="beforeImageUpload"
              :http-request="opt => handleMultiUpload(opt, 'miniShot')"
              :on-remove="(f, fl) => onMultiRemove(f, fl, 'miniShot')"
            >
              <el-icon><Plus /></el-icon>
            </el-upload>
            <p class="field-tip">请提供展示商品/服务的页面截图/设计稿(最多5张),若小程序胃建设完善或未上线请务必提供。</p>
          </div>
        </el-form-item>
      </section>

      <!-- APP -->
      <section v-if="form.scenarios.app" class="form-block sub-section">
        <h2 class="section-head">
          <span class="section-bar" aria-hidden="true" />
          APP<span class="section-intro">你选择了“APP”场景，入驻成功后，服务商可帮商户发起APP支付</span>
        </h2>

        <el-form-item label="AppID 来源" prop="appIdSource" required>
          <el-radio-group v-model="form.appIdSource">
            <el-radio value="provider"> 服务商应用 AppID </el-radio>
            <el-radio value="merchant"> 商家应用 AppID </el-radio>
          </el-radio-group>
        </el-form-item>

        <el-form-item v-if="form.appIdSource === 'provider'" label="服务商应用 AppID" prop="providerAppId">
          <div class="field-stack">
            <el-select v-model="form.providerAppId" placeholder="请选择" filterable style="max-width: 480px">
              <el-option v-for="o in providerAppOptions" :key="o.value" :label="o.label" :value="o.value" />
            </el-select>
          </div>
        </el-form-item>

        <el-form-item prop="appShotUrls">
          <template #label>
            <span class="label-with-icon">
              APP 截图
              <el-tooltip placement="top" content="首页、商品列表、详情、支付等相关页面。">
                <el-icon class="info-icon" :size="16"><InfoFilled /></el-icon>
              </el-tooltip>
            </span>
          </template>
          <div class="field-stack">
            <el-upload
              v-model:file-list="form.appShotFileList"
              list-type="picture-card"
              :limit="5"
              accept=".jpg,.jpeg,.png,.bmp,image/jpeg,image/png,image/bmp"
              :before-upload="beforeImageUpload"
              :http-request="opt => handleMultiUpload(opt, 'appShot')"
              :on-remove="(f, fl) => onMultiRemove(f, fl, 'appShot')"
            >
              <el-icon><Plus /></el-icon>
            </el-upload>
            <p class="field-tip">请提供APP首页截图，尾页截图，应用内截图，支付页截图各1张</p>
          </div>
        </el-form-item>
      </section>
    </el-form>

    <div class="footer-actions">
      <el-button @click="onBack"> 返回 </el-button>
      <el-button type="primary" class="btn-next" :loading="submitLoading" @click="onSubmit"> 确定 </el-button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed, nextTick, onMounted, reactive, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import type { FormInstance, FormRules } from "element-plus";
import type { UploadRequestOptions, UploadUserFile } from "element-plus";
import { Plus, InfoFilled } from "@element-plus/icons-vue";
import {
  uploadBusinessInfoImageToOss,
  filterOutUploadUserFileByUid,
  failBusinessInfoUploadCleanup,
  collectSuccessUploadUrls
} from "@/utils/businessInfoImageUpload";
import { localGet, localSet } from "@/utils/index";
import cityJson from "@/assets/json/city.json";

const BUSINESS_DATA_CACHE_KEY = "businessData";

const formRef = ref<FormInstance>();
const submitLoading = ref(false);
const route = useRoute();
const router = useRouter();

type UploadKind = "storefront" | "interior" | "miniShot" | "appShot";

const form = reactive({
  merchantShortName: "",
  servicePhone: "",
  scenarios: {
    offline: true,
    miniProgram: true,
    app: true,
    wechatAccount: false,
    website: false,
    wecom: false
  },
  offlineVenueName: "",
  offlineProvince: "",
  offlineCity: "",
  offlineAddress: "",
  offlineStorefrontFileList: [] as UploadUserFile[],
  offlineStorefrontUrls: [] as string[],
  offlineInteriorFileList: [] as UploadUserFile[],
  offlineInteriorUrls: [] as string[],
  offlineMpAppId: "",
  miniProgramAppId: "",
  miniProgramShotFileList: [] as UploadUserFile[],
  miniProgramShotUrls: [] as string[],
  appIdSource: "provider" as "provider" | "merchant",
  providerAppId: "",
  merchantAppId: "",
  appShotFileList: [] as UploadUserFile[],
  appShotUrls: [] as string[],
  /** 用于校验至少选一种经营场景 */
  scenarioBits: "1"
});

/** 与进件文档 business_info.sales_info.sales_scenes_type 枚举对应 */
function buildSalesScenesType(): string[] {
  const s = form.scenarios;
  const types: string[] = [];
  if (s.offline) types.push("SALES_SCENES_STORE");
  if (s.miniProgram) types.push("SALES_SCENES_MINI_PROGRAM");
  if (s.app) types.push("SALES_SCENES_APP");
  if (s.wechatAccount) types.push("SALES_SCENES_MP");
  if (s.website) types.push("SALES_SCENES_WEB");
  if (s.wecom) types.push("SALES_SCENES_WEWORK");
  return types;
}

/** 上传组件更新 file-list 时可能丢 response，用 uid 备份 media_id */
const wechatMediaIdByFileUid = new Map<number, string>();

function getMediaIdFromUploadFile(f: UploadUserFile): string {
  const url = String((f as UploadUserFile & { url?: string }).url ?? "").trim();
  const tagged = String((f as UploadUserFile & { __wxMediaId?: string }).__wxMediaId ?? "").trim();
  if (tagged) return tagged;
  const uid = (f as UploadUserFile & { uid?: number }).uid;
  if (uid != null && wechatMediaIdByFileUid.has(uid)) {
    return wechatMediaIdByFileUid.get(uid) ?? "";
  }
  const r = (f as UploadUserFile & { response?: unknown }).response;
  if (r && typeof r === "object" && !Array.isArray(r)) {
    const o = r as Record<string, unknown>;
    const direct = String(o.media_id ?? o.mediaId ?? o.url ?? "").trim();
    if (direct) return direct;
    const nested = extractMediaUploadMeta(r).mediaId;
    if (nested) return nested;
  }
  return url;
}

/** 写入进件缓存的四处图片字段（OSS 上传后的可访问地址） */
function collectMediaIdsForMerge(list: UploadUserFile[]): string[] {
  return list
    .filter(f => f.status === "success")
    .map(f => getMediaIdFromUploadFile(f))
    .filter(Boolean);
}

function mergeManageInfoIntoBusinessDataCache() {
  const prev = localGet(BUSINESS_DATA_CACHE_KEY);
  const base = prev && typeof prev === "object" && !Array.isArray(prev) ? { ...(prev as Record<string, unknown>) } : {};
  const prevBi =
    base.business_info && typeof base.business_info === "object" && !Array.isArray(base.business_info)
      ? { ...(base.business_info as Record<string, unknown>) }
      : {};
  const prevSales =
    prevBi.sales_info && typeof prevBi.sales_info === "object" && !Array.isArray(prevBi.sales_info)
      ? { ...(prevBi.sales_info as Record<string, unknown>) }
      : {};

  const sales_info: Record<string, unknown> = {
    ...prevSales,
    sales_scenes_type: buildSalesScenesType()
  };
  if (form.scenarios.offline) {
    sales_info.biz_store_info = {
      biz_store_name: String(form.offlineVenueName || "").trim(),
      biz_address_code: String(form.offlineCity || "").trim(),
      biz_store_address: String(form.offlineAddress || "").trim(),
      store_entrance_pic: collectMediaIdsForMerge(form.offlineStorefrontFileList),
      indoor_pic: collectMediaIdsForMerge(form.offlineInteriorFileList),
      biz_sub_appid: String(form.offlineMpAppId || "").trim()
    };
  } else {
    delete sales_info.biz_store_info;
  }

  if (form.scenarios.miniProgram) {
    sales_info.mini_program_info = {
      mini_program_sub_appid: String(form.miniProgramAppId || "").trim(),
      mini_program_pics: collectMediaIdsForMerge(form.miniProgramShotFileList)
    };
  } else {
    delete sales_info.mini_program_info;
  }

  if (form.scenarios.app) {
    sales_info.app_info = {
      app_appid: String(form.providerAppId || "").trim(),
      app_sub_appid: String(form.merchantAppId || "").trim(),
      app_pics: collectMediaIdsForMerge(form.appShotFileList)
    };
  } else {
    delete sales_info.app_info;
  }

  base.business_info = {
    ...prevBi,
    merchant_shortname: String(form.merchantShortName || "").trim(),
    service_phone: String(form.servicePhone || "").trim(),
    sales_info
  };
  localSet(BUSINESS_DATA_CACHE_KEY, base);
}

type CityJsonEntry = { name: string; adCode: string; cityCode: string; type?: string };
type CityJsonRoot = { cityList: { letter: string; list: CityJsonEntry[] }[] };

/** GB 行政区划代码前两位 → 省级名称（与 city.json 中 adCode 对应） */
const PROVINCE_ROWS: [string, string][] = [
  ["11", "北京市"],
  ["12", "天津市"],
  ["13", "河北省"],
  ["14", "山西省"],
  ["15", "内蒙古自治区"],
  ["21", "辽宁省"],
  ["22", "吉林省"],
  ["23", "黑龙江省"],
  ["31", "上海市"],
  ["32", "江苏省"],
  ["33", "浙江省"],
  ["34", "安徽省"],
  ["35", "福建省"],
  ["36", "江西省"],
  ["37", "山东省"],
  ["41", "河南省"],
  ["42", "湖北省"],
  ["43", "湖南省"],
  ["44", "广东省"],
  ["45", "广西壮族自治区"],
  ["46", "海南省"],
  ["50", "重庆市"],
  ["51", "四川省"],
  ["52", "贵州省"],
  ["53", "云南省"],
  ["54", "西藏自治区"],
  ["61", "陕西省"],
  ["62", "甘肃省"],
  ["63", "青海省"],
  ["64", "宁夏回族自治区"],
  ["65", "新疆维吾尔自治区"],
  ["71", "台湾省"],
  ["81", "香港特别行政区"],
  ["82", "澳门特别行政区"]
];
const PROVINCE_BY_PREFIX = Object.fromEntries(PROVINCE_ROWS) as Record<string, string>;
const PROVINCE_PREFIX_ORDER = PROVINCE_ROWS.map(([k]) => k);

function buildProvinceCityOptions(): {
  label: string;
  value: string;
  cities: { label: string; value: string }[];
}[] {
  const root = cityJson as CityJsonRoot;
  const flat = root.cityList.flatMap(g => g.list);
  const bucket = new Map<string, Map<string, CityJsonEntry>>();
  for (const c of flat) {
    const digits = String(c.adCode ?? "").replace(/\D/g, "");
    const code = digits.length >= 6 ? digits.slice(-6) : digits.padStart(6, "0");
    if (code.length !== 6) continue;
    const prefix = code.slice(0, 2);
    if (!PROVINCE_BY_PREFIX[prefix]) continue;
    if (!bucket.has(prefix)) bucket.set(prefix, new Map());
    bucket.get(prefix)!.set(c.adCode, c);
  }
  const out = [...bucket.entries()].map(([prefix, cityMap]) => ({
    value: prefix,
    label: PROVINCE_BY_PREFIX[prefix],
    cities: [...cityMap.values()]
      .sort((a, b) => a.name.localeCompare(b.name, "zh-CN"))
      .map(ci => ({ label: ci.name, value: ci.adCode }))
  }));
  out.sort((a, b) => {
    const ia = PROVINCE_PREFIX_ORDER.indexOf(a.value);
    const ib = PROVINCE_PREFIX_ORDER.indexOf(b.value);
    return (ia === -1 ? 999 : ia) - (ib === -1 ? 999 : ib);
  });
  return out;
}

const provinceOptions = buildProvinceCityOptions();

const cityOptions = ref<{ label: string; value: string }[]>([]);

watch(
  () => form.offlineProvince,
  code => {
    form.offlineCity = "";
    const p = provinceOptions.find(x => x.value === code);
    cityOptions.value = p?.cities ?? [];
  }
);

const providerAppOptions = [
  { label: "请选择", value: "" },
  { label: "wxf5f1efe3a9f5012e", value: "wxf5f1efe3a9f5012e" }
];

const scenarioChecked = computed(() => {
  const s = form.scenarios;
  return s.offline || s.miniProgram || s.app || s.wechatAccount || s.website || s.wecom;
});

watch(
  () => form.scenarios,
  () => {
    form.scenarioBits = scenarioChecked.value ? "1" : "";
  },
  { deep: true }
);

function asStringArray(v: unknown): string[] {
  if (!Array.isArray(v)) return [];
  return v.map(x => String(x ?? "").trim()).filter(Boolean);
}

/** 从 localStorage businessData.business_info 回显经营信息表单 */
function hydrateManageFormFromBusinessDataCache() {
  const raw = localGet(BUSINESS_DATA_CACHE_KEY) as Record<string, unknown> | null | undefined;
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
  const bi = raw.business_info as Record<string, unknown> | undefined;
  if (!bi || typeof bi !== "object" || Array.isArray(bi)) return;

  form.merchantShortName = String(bi.merchant_shortname ?? "").trim();
  form.servicePhone = String(bi.service_phone ?? "").trim();

  let pendingOfflineCity = "";

  const si = bi.sales_info as Record<string, unknown> | undefined;
  if (si && typeof si === "object" && !Array.isArray(si)) {
    const sceneTypes = si.sales_scenes_type;
    if (Array.isArray(sceneTypes)) {
      const set = new Set(sceneTypes.map(t => String(t)));
      form.scenarios.offline = set.has("SALES_SCENES_STORE");
      form.scenarios.miniProgram = set.has("SALES_SCENES_MINI_PROGRAM");
      form.scenarios.app = set.has("SALES_SCENES_APP");
      form.scenarios.wechatAccount = set.has("SALES_SCENES_MP");
      form.scenarios.website = set.has("SALES_SCENES_WEB");
      form.scenarios.wecom = set.has("SALES_SCENES_WEWORK");
      form.scenarioBits = scenarioChecked.value ? "1" : "";
    }

    const bzs = si.biz_store_info as Record<string, unknown> | undefined;
    if (bzs && typeof bzs === "object" && !Array.isArray(bzs)) {
      form.offlineVenueName = String(bzs.biz_store_name ?? "").trim();
      const digits = String(bzs.biz_address_code ?? "").replace(/\D/g, "");
      const code6 = digits.length >= 6 ? digits.slice(-6) : digits;
      if (code6.length >= 2) {
        const prefix = code6.slice(0, 2);
        form.offlineProvince = prefix;
        const p = provinceOptions.find(x => x.value === prefix);
        cityOptions.value = p?.cities ?? [];
        if (code6.length >= 6) pendingOfflineCity = code6;
      }
      form.offlineAddress = String(bzs.biz_store_address ?? "").trim();
      const se = asStringArray(bzs.store_entrance_pic);
      const indoor = asStringArray(bzs.indoor_pic);
      form.offlineStorefrontUrls = [...se];
      form.offlineInteriorUrls = [...indoor];
      form.offlineStorefrontFileList = se.map(
        (url, i) =>
          ({
            name: `storefront-${i + 1}.jpg`,
            url,
            status: "success",
            response: { url, media_id: url, mediaId: url },
            __wxMediaId: url
          }) as UploadUserFile
      );
      form.offlineInteriorFileList = indoor.map(
        (url, i) =>
          ({
            name: `interior-${i + 1}.jpg`,
            url,
            status: "success",
            response: { url, media_id: url, mediaId: url },
            __wxMediaId: url
          }) as UploadUserFile
      );
      form.offlineMpAppId = String(bzs.biz_sub_appid ?? "").trim();
    }

    const mpi = si.mini_program_info as Record<string, unknown> | undefined;
    if (mpi && typeof mpi === "object" && !Array.isArray(mpi)) {
      form.miniProgramAppId = String(mpi.mini_program_sub_appid ?? "").trim();
      const pics = asStringArray(mpi.mini_program_pics);
      form.miniProgramShotUrls = [...pics];
      form.miniProgramShotFileList = pics.map(
        (url, i) =>
          ({
            name: `mini-${i + 1}.jpg`,
            url,
            status: "success",
            response: { url, media_id: url, mediaId: url },
            __wxMediaId: url
          }) as UploadUserFile
      );
    }

    const appInf = si.app_info as Record<string, unknown> | undefined;
    if (appInf && typeof appInf === "object" && !Array.isArray(appInf)) {
      form.providerAppId = String(appInf.app_appid ?? "").trim();
      form.merchantAppId = String(appInf.app_sub_appid ?? "").trim();
      form.appIdSource = form.providerAppId ? "provider" : "merchant";
      const pics = asStringArray(appInf.app_pics);
      form.appShotUrls = [...pics];
      form.appShotFileList = pics.map(
        (url, i) =>
          ({
            name: `app-${i + 1}.jpg`,
            url,
            status: "success",
            response: { url, media_id: url, mediaId: url },
            __wxMediaId: url
          }) as UploadUserFile
      );
    }
  }

  nextTick(() => {
    if (pendingOfflineCity) form.offlineCity = pendingOfflineCity;
    formRef.value?.clearValidate();
  });
}

onMounted(() => {
  hydrateManageFormFromBusinessDataCache();
});

const rules: FormRules = {
  merchantShortName: [{ required: true, message: "请输入商户简称", trigger: "blur" }],
  servicePhone: [
    { required: true, message: "请输入客服电话", trigger: "blur" },
    { pattern: /^[\d\-+\s]{5,20}$/, message: "请输入有效电话号码", trigger: "blur" }
  ],
  scenarioBits: [{ required: true, message: "请至少选择一种经营场景", trigger: "change" }],
  offlineVenueName: [
    {
      validator: (_r, _v, cb) => {
        if (form.scenarios.offline && !String(form.offlineVenueName).trim()) {
          cb(new Error("请输入线下场所名称"));
        } else cb();
      },
      trigger: "blur"
    }
  ],
  offlineProvinceCity: [
    {
      validator: (_r, _v, cb) => {
        if (form.scenarios.offline && (!form.offlineProvince || !form.offlineCity)) {
          cb(new Error("请选择线下场所省市"));
        } else cb();
      },
      trigger: "change"
    }
  ],
  offlineAddress: [
    {
      validator: (_r, _v, cb) => {
        if (form.scenarios.offline && !String(form.offlineAddress).trim()) {
          cb(new Error("请输入线下场所地址"));
        } else cb();
      },
      trigger: "blur"
    }
  ],
  offlineStorefrontUrls: [
    {
      validator: (_r, _v, cb) => {
        if (form.scenarios.offline && form.offlineStorefrontUrls.length < 1) {
          cb(new Error("请上传门头照片"));
        } else cb();
      },
      trigger: "change"
    }
  ],
  offlineInteriorUrls: [
    {
      validator: (_r, _v, cb) => {
        if (form.scenarios.offline && form.offlineInteriorUrls.length < 1) {
          cb(new Error("请上传内部照片"));
        } else cb();
      },
      trigger: "change"
    }
  ],
  miniProgramAppId: [
    {
      validator: (_r, _v, cb) => {
        if (form.scenarios.miniProgram && !String(form.miniProgramAppId).trim()) {
          cb(new Error("请输入小程序 APPID"));
        } else cb();
      },
      trigger: "blur"
    }
  ],
  providerAppId: [
    {
      validator: (_r, _v, cb) => {
        if (form.scenarios.app && form.appIdSource === "provider" && !String(form.providerAppId).trim()) {
          cb(new Error("请选择服务商应用 AppID"));
        } else cb();
      },
      trigger: "change"
    }
  ],
  appShotUrls: [
    {
      validator: (_r, _v, cb) => {
        if (form.scenarios.app && form.appShotUrls.length < 1) {
          cb(new Error("请上传 APP 截图"));
        } else cb();
      },
      trigger: "change"
    }
  ]
};

const MAX_IMG_MB = 20;

function beforeImageUpload(file: File) {
  const name = file.name?.toLowerCase() || "";
  const okExt = /\.(jpe?g|png|bmp)$/i.test(name);
  const okMime = ["image/jpeg", "image/png", "image/bmp"].includes(file.type);
  if (!okExt && !okMime) {
    ElMessage.error("图片只支持 JPG、BMP、PNG 格式");
    return false;
  }
  if (file.size / 1024 / 1024 > MAX_IMG_MB) {
    ElMessage.error(`文件大小不能超过 ${MAX_IMG_MB}M`);
    return false;
  }
  return true;
}

function urlListKey(kind: UploadKind): "offlineStorefrontUrls" | "offlineInteriorUrls" | "miniProgramShotUrls" | "appShotUrls" {
  const map = {
    storefront: "offlineStorefrontUrls",
    interior: "offlineInteriorUrls",
    miniShot: "miniProgramShotUrls",
    appShot: "appShotUrls"
  } as const;
  return map[kind];
}

/** 解析历史微信 media/upload 缓存（兼容旧数据） */
function extractMediaUploadMeta(res: any): { fileUrl: string; mediaId: string; errMsg: string } {
  const errMsg = String(res?.msg ?? res?.message ?? res?.data?.msg ?? res?.data?.message ?? "").trim();

  const pickFromObject = (node: any): { fileUrl: string; mediaId: string } => {
    if (!node || typeof node !== "object" || Array.isArray(node)) return { fileUrl: "", mediaId: "" };
    const mediaId = String(node.media_id ?? node.mediaId ?? node.MediaId ?? "").trim();
    const fileUrl = String(node.url ?? node.fileUrl ?? node.mediaUrl ?? node.picUrl ?? "").trim();
    return { fileUrl, mediaId };
  };

  let root = res;
  const rawData = root?.data;
  if (rawData != null && typeof rawData === "string") {
    const s = rawData.trim();
    if (s.startsWith("{") || s.startsWith("[")) {
      try {
        root = { ...(root && typeof root === "object" ? root : {}), data: JSON.parse(s) };
      } catch {
        return { fileUrl: "", mediaId: s, errMsg };
      }
    } else if (s) {
      return { fileUrl: "", mediaId: s, errMsg };
    }
  }

  const candidates: any[] = [];
  const push = (x: any) => {
    if (x != null) candidates.push(x);
  };
  push(root);
  push(root?.data);
  if (root?.data != null && typeof root.data === "object" && !Array.isArray(root.data)) {
    push(root.data.data);
    for (const v of Object.values(root.data)) {
      if (v && typeof v === "object" && !Array.isArray(v)) push(v);
    }
  }

  let fileUrl = "";
  let mediaId = "";
  for (const c of candidates) {
    if (typeof c === "string" && c.trim()) {
      mediaId = c.trim();
      break;
    }
    if (!c || typeof c !== "object") continue;
    const p = pickFromObject(c);
    if (p.mediaId) {
      mediaId = p.mediaId;
      fileUrl = p.fileUrl || fileUrl;
      break;
    }
    for (const v of Object.values(c)) {
      if (v && typeof v === "object" && !Array.isArray(v)) {
        const p2 = pickFromObject(v);
        if (p2.mediaId) {
          mediaId = p2.mediaId;
          fileUrl = p2.fileUrl || fileUrl;
          break;
        }
      }
    }
    if (mediaId) break;
  }

  return { fileUrl, mediaId, errMsg };
}

function fileListForKind(kind: UploadKind): UploadUserFile[] {
  if (kind === "storefront") return form.offlineStorefrontFileList;
  if (kind === "interior") return form.offlineInteriorFileList;
  if (kind === "miniShot") return form.miniProgramShotFileList;
  return form.appShotFileList;
}

function setFileListForKind(kind: UploadKind, list: UploadUserFile[]) {
  if (kind === "storefront") form.offlineStorefrontFileList = list;
  else if (kind === "interior") form.offlineInteriorFileList = list;
  else if (kind === "miniShot") form.miniProgramShotFileList = list;
  else form.appShotFileList = list;
}

async function handleMultiUpload(options: UploadRequestOptions, kind: UploadKind) {
  const uploadFileItem = options.file as UploadUserFile;
  const uid = uploadFileItem.uid;
  const raw = uploadFileItem.raw || uploadFileItem;
  const file = raw instanceof File ? raw : null;
  if (!file) {
    setFileListForKind(kind, filterOutUploadUserFileByUid(fileListForKind(kind), uid));
    options.onError(new Error("无效文件") as any);
    return;
  }

  uploadFileItem.status = "uploading";
  try {
    const { fileUrl, mediaId } = await uploadBusinessInfoImageToOss(file, { showUploadOverlay: false });
    uploadFileItem.status = "success";
    uploadFileItem.url = fileUrl;
    uploadFileItem.response = { media_id: mediaId, url: fileUrl };
    const uidNum = (uploadFileItem as UploadUserFile & { uid?: number }).uid;
    if (uidNum != null) wechatMediaIdByFileUid.set(uidNum, fileUrl);
    (uploadFileItem as UploadUserFile & { __wxMediaId?: string }).__wxMediaId = fileUrl;
    options.onSuccess({ url: fileUrl } as any);
  } catch (err) {
    failBusinessInfoUploadCleanup({
      fileList: fileListForKind(kind),
      setFileList: list => setFileListForKind(kind, list),
      uid,
      error: err
    });
    options.onError(err as any);
  }
  syncUrlsFromFileList(kind);
  validateKind(kind);
}

function syncUrlsFromFileList(kind: UploadKind) {
  const key = urlListKey(kind);
  form[key] = collectSuccessUploadUrls(fileListForKind(kind));
}

function onMultiRemove(file: UploadUserFile, _fileList: UploadUserFile[], kind: UploadKind) {
  const uid = (file as UploadUserFile & { uid?: number }).uid;
  if (uid != null) wechatMediaIdByFileUid.delete(uid);
  delete (file as UploadUserFile & { __wxMediaId?: string }).__wxMediaId;
  syncUrlsFromFileList(kind);
  validateKind(kind);
}

function syncAllUploadUrlArrays() {
  syncUrlsFromFileList("storefront");
  syncUrlsFromFileList("interior");
  syncUrlsFromFileList("miniShot");
  syncUrlsFromFileList("appShot");
}

function assertActiveScenarioPicsHaveMediaId(): boolean {
  const groups: { label: string; list: UploadUserFile[] }[] = [];
  if (form.scenarios.offline) {
    groups.push({ label: "门头照片", list: form.offlineStorefrontFileList });
    groups.push({ label: "内部照片", list: form.offlineInteriorFileList });
  }
  if (form.scenarios.miniProgram) {
    groups.push({ label: "小程序截图", list: form.miniProgramShotFileList });
  }
  if (form.scenarios.app) {
    groups.push({ label: "APP 截图", list: form.appShotFileList });
  }
  for (const g of groups) {
    for (const f of g.list) {
      if (f.status !== "success") continue;
      if (!getMediaIdFromUploadFile(f)) {
        ElMessage.warning(`「${g.label}」请完成图片上传`);
        return false;
      }
    }
  }
  return true;
}

function validateKind(kind: UploadKind) {
  const map: Partial<Record<UploadKind, string>> = {
    storefront: "offlineStorefrontUrls",
    interior: "offlineInteriorUrls",
    appShot: "appShotUrls"
  };
  const f = map[kind];
  if (f) formRef.value?.validateField(f).catch(() => {});
}

function openScenarioGuide() {
  ElMessage.info("经营场景填写指引（可对接文档）");
}
function openStorefrontGuide() {
  ElMessage.info("门头照片拍摄指引");
}
function openAppidGuide() {
  ElMessage.info("公众号 APPID 查找说明");
}
function openMiniAppidGuide() {
  ElMessage.info("小程序 APPID 说明");
}
function openProviderAppGuide() {
  ElMessage.info("服务商应用说明");
}
function openMerchantAppGuide() {
  ElMessage.info("商家应用说明");
}
function openAppShotExample() {
  ElMessage.info("APP 截图示例");
}

function onBack() {
  router.push({ path: "/businessInfo/dataEntry", query: { ...route.query } });
}

function onSaveDraft() {
  ElMessage.success("草稿已保存（可对接接口）");
}

async function onSubmit() {
  if (!formRef.value) return;
  submitLoading.value = true;
  try {
    await formRef.value.validate();
    syncAllUploadUrlArrays();
    if (!assertActiveScenarioPicsHaveMediaId()) return;
    mergeManageInfoIntoBusinessDataCache();
    await router.push({
      path: "/businessInfo/industryQualifications",
      query: { ...route.query }
    });
  } catch {
    ElMessage.warning("请完善必填信息");
  } finally {
    submitLoading.value = false;
  }
}
</script>

<style scoped lang="scss">
$wechat-green: #07c160;
.manage-info-page {
  box-sizing: border-box;
  min-height: 100%;
  padding: 24px 32px 40px;
  margin: 0 auto;
  background: #ffffff;
}
.page-title {
  margin: 0 0 24px;
  font-size: 20px;
  font-weight: 600;
  color: #303133;
}
.manage-form {
  :deep(.el-form-item__label) {
    font-weight: 500;
    color: #606266;
  }
}
.form-block {
  padding-bottom: 16px;
  margin-bottom: 28px;
  border-bottom: 1px solid #ebeef5;
  &:last-of-type {
    border-bottom: none;
  }
}
.sub-section {
  padding: 8px 16px 20px;
  margin-top: 8px;
  background: #fafafa;
  border: 1px solid #ebeef5;
  border-radius: 8px;
}
.section-head {
  display: flex;
  align-items: center;
  margin: 16px 0 20px;
  font-size: 16px;
  font-weight: 600;
  color: #303133;
}
.section-bar {
  display: inline-block;
  flex-shrink: 0;
  width: 4px;
  height: 16px;
  margin-right: 8px;
  background: $wechat-green;
  border-radius: 2px;
}
.section-intro {
  padding-left: 20px;
  font-size: 12px;
  color: #999999;
}
.field-stack {
  width: 100%;
}
.field-tip {
  margin: 8px 0 0;
  font-size: 12px;
  line-height: 1.65;
  color: #909399;
}
.scenario-checks {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 20px;
  margin-bottom: 8px;
}
.label-with-icon {
  display: inline-flex;
  gap: 4px;
  align-items: center;
}
.info-icon {
  color: #909399;
  cursor: help;
}
:deep(.el-upload--picture-card) {
  --el-upload-picture-card-size: 120px;
}
:deep(.el-upload-list--picture-card .el-upload-list__item) {
  width: 120px;
  height: 120px;
}
.footer-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  padding-top: 24px;
  margin-top: 16px;
  border-top: 1px solid #ebeef5;
}
.footer-center {
  display: flex;
  flex: 1;
  justify-content: center;
}
.btn-next {
  min-width: 100px;
  background: $wechat-green;
  border-color: $wechat-green;
  &:hover {
    background: #06ad56;
    border-color: #06ad56;
  }
}
</style>
