<template>
  <div class="space-y-2">
    <div class="flex items-center justify-between bg-white px-4 py-2 rounded-lg shadow-sm border border-gray-200">
      <div>
        <h3 class="text-base font-bold text-gray-800">Jenkins 一键构建</h3>
        <p class="text-xs text-gray-500">勾选 Jenkins 通知项目，调整参数后批量触发 Build</p>
      </div>
      <div class="flex items-center gap-2">
        <div
          v-if="refreshing"
          class="inline-flex items-center gap-1.5 rounded border border-blue-100 bg-blue-50 px-2.5 py-1.5 text-xs text-blue-700"
        >
          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-4 w-4 animate-spin">
            <path fill-rule="evenodd" d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0v2.433l-.31-.31a7 7 0 00-11.712 3.138.75.75 0 001.449.39 5.5 5.5 0 019.201-2.466l.312.312h-2.433a.75.75 0 000 1.5h4.185a.75.75 0 00.53-.219z" clip-rule="evenodd" />
          </svg>
          正在刷新
        </div>
        <button
          @click="refreshProjects()"
          :disabled="refreshing || triggering"
          class="px-2.5 py-1.5 bg-gray-100 text-gray-700 text-xs font-medium rounded hover:bg-gray-200 disabled:opacity-50 flex items-center gap-1"
        >
          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-4 h-4" :class="{'animate-spin': refreshing}">
            <path fill-rule="evenodd" d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0v2.433l-.31-.31a7 7 0 00-11.712 3.138.75.75 0 001.449.39 5.5 5.5 0 019.201-2.466l.312.312h-2.433a.75.75 0 000 1.5h4.185a.75.75 0 00.53-.219z" clip-rule="evenodd" />
          </svg>
          刷新
        </button>
        <button
          @click="triggerBuilds"
          :disabled="triggering || selectedProjects.length === 0"
          class="px-3 py-1.5 bg-blue-600 text-white text-xs font-medium rounded hover:bg-blue-700 disabled:opacity-50 flex items-center gap-1"
        >
          <svg v-if="!triggering" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-4 h-4">
            <path d="M6.3 2.84A1.5 1.5 0 004 4.11v11.78a1.5 1.5 0 002.3 1.27l9.34-5.89a1.5 1.5 0 000-2.54L6.3 2.84z" />
          </svg>
          <svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-4 h-4 animate-spin">
            <path fill-rule="evenodd" d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39z" clip-rule="evenodd" />
          </svg>
          {{ triggering ? '触发中...' : `触发选中 (${selectedProjects.length})` }}
        </button>
      </div>
    </div>

    <div v-if="message" class="text-xs text-green-600 bg-green-50 px-3 py-2 rounded border border-green-100">{{ message }}</div>
    <div v-if="error" class="text-xs text-red-600 bg-red-50 px-3 py-2 rounded border border-red-100">{{ error }}</div>

    <div class="grid grid-cols-1 xl:grid-cols-[minmax(0,1fr)_24rem] gap-2 items-start">
      <div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
        <div class="bg-gray-50 px-4 py-2 border-b border-gray-200 flex justify-between items-center">
          <div class="flex items-center gap-2">
            <h4 class="font-semibold text-gray-700 text-sm">可构建项目</h4>
            <span class="text-xs text-gray-400">{{ projects.length }} 个项目</span>
          </div>
          <label v-if="projects.length > 0" class="flex items-center gap-1.5 text-xs text-gray-600 cursor-pointer">
            <input type="checkbox" :checked="isAllSelected" @change="toggleAll" class="rounded border-gray-300 text-blue-600 focus:ring-blue-500" />
            全选
          </label>
        </div>

        <div v-if="loading" class="p-10 text-center text-sm text-gray-400">正在后台同步 Jenkins 项目，页面不会被阻塞...</div>
        <div v-else-if="projects.length === 0" class="p-10 text-center text-sm text-gray-400">
          暂无启用 Jenkins 发布通知且配置 Job 名称的项目
        </div>

        <div v-else class="overflow-x-auto">
          <table class="min-w-full divide-y divide-gray-100 table-fixed">
            <thead class="bg-gray-50">
              <tr>
                <th class="w-60 px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">项目 / Job</th>
                <th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Build 参数</th>
              </tr>
            </thead>
            <tbody class="divide-y divide-gray-100 bg-white">
              <tr v-for="project in projects" :key="project.slug" class="hover:bg-gray-50 align-top">
                <td class="px-4 py-2">
                  <label class="flex items-start gap-2 cursor-pointer">
                    <input v-model="project.selected" type="checkbox" class="mt-0.5 rounded border-gray-300 text-blue-600 focus:ring-blue-500" @change="savePreferences" />
                    <span class="min-w-0 leading-tight">
                      <span class="flex items-center gap-1.5 min-w-0">
                        <span class="font-mono text-sm font-semibold text-gray-800 truncate">{{ project.slug }}</span>
                        <span class="text-[11px] text-gray-500 truncate" :title="project.name">{{ project.name }}</span>
                      </span>
                      <a
                        v-if="jenkinsPageUrl(project)"
                        :href="jenkinsPageUrl(project)"
                        target="_blank"
                        rel="noopener noreferrer"
                        class="mt-0.5 inline-flex max-w-full items-center gap-1 font-mono text-[11px] text-blue-600 hover:text-blue-800 hover:underline"
                        :title="'打开 Jenkins 发布页：' + project.jenkins_job_name"
                        @click.stop
                      >
                        <span class="truncate">{{ project.jenkins_job_name }}</span>
                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-3 w-3 shrink-0">
                          <path fill-rule="evenodd" d="M4.25 5.5a.75.75 0 00-.75.75v8.5c0 .414.336.75.75.75h8.5a.75.75 0 00.75-.75v-4a.75.75 0 011.5 0v4A2.25 2.25 0 0112.75 17h-8.5A2.25 2.25 0 012 14.75v-8.5A2.25 2.25 0 014.25 4h5a.75.75 0 010 1.5h-5z" clip-rule="evenodd" />
                          <path fill-rule="evenodd" d="M6.194 12.753a.75.75 0 001.06.053L16.5 4.44v2.81a.75.75 0 001.5 0v-4.5a.75.75 0 00-.75-.75h-4.5a.75.75 0 000 1.5h2.553l-9.056 8.194a.75.75 0 00-.053 1.06z" clip-rule="evenodd" />
                        </svg>
                      </a>
                      <span v-else class="block font-mono text-[11px] text-gray-400 truncate" :title="project.jenkins_job_name">{{ project.jenkins_job_name }}</span>
                    </span>
                  </label>
                </td>
                <td class="px-4 py-2">
                  <div v-if="project.parameters.length === 0" class="text-xs text-gray-400 leading-7">
                    无参数，直接触发 build
                  </div>

                  <div v-else class="space-y-2">
                    <div class="grid grid-cols-[7rem_9rem_5.5rem_8rem] gap-2 items-start">
                      <template v-for="parameterName in primaryParameterOrder" :key="parameterName">
                        <parameter-control
                          v-if="parameterByName(project, parameterName)"
                          :project="project"
                          :parameter="parameterByName(project, parameterName)"
                          :triggering="triggering"
                          class="w-full"
                          @save="savePreferences"
                          @toggle-multi="toggleMultiValue"
                        />
                        <div v-else class="h-8"></div>
                      </template>
                    </div>

                    <div v-if="hasSecondaryParameters(project)" class="flex flex-wrap gap-2 border-t border-gray-100 pt-2">
                      <div class="grid grid-cols-[7rem_minmax(0,1fr)] gap-2 items-start w-full">
                        <parameter-control
                          v-if="parameterByName(project, 'region')"
                          :project="project"
                          :parameter="parameterByName(project, 'region')"
                          :triggering="triggering"
                          class="w-full"
                          @save="savePreferences"
                          @toggle-multi="toggleMultiValue"
                        />
                        <div v-else class="min-h-[4.25rem]"></div>

                        <parameter-control
                          v-if="parameterByName(project, 'project')"
                          :project="project"
                          :parameter="parameterByName(project, 'project')"
                          :triggering="triggering"
                          class="w-full"
                          @save="savePreferences"
                          @toggle-multi="toggleMultiValue"
                        />
                        <div v-else class="min-h-[4.25rem]"></div>
                      </div>
                    </div>
                  </div>
                </td>
              </tr>
            </tbody>
          </table>
        </div>
      </div>

      <aside class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
        <div class="bg-gray-50 px-3 py-2 border-b border-gray-200 flex items-center justify-between">
          <h4 class="font-semibold text-gray-700 text-sm">发布记录</h4>
          <span class="text-xs text-gray-400">{{ operationRecords.length }}</span>
        </div>
        <div v-if="operationRecords.length === 0" class="p-4 text-xs text-gray-400">
          暂无发布记录
        </div>
        <div v-else class="divide-y divide-gray-100 max-h-[calc(100vh-13rem)] overflow-y-auto">
          <div v-for="record in operationRecords" :key="record.id" class="px-3 py-2">
            <div class="flex items-center justify-between gap-2">
              <span class="font-mono text-[11px] text-gray-500">{{ record.time }}</span>
              <div class="flex items-center gap-1.5">
                <span class="rounded px-1.5 py-0.5 text-[11px]" :class="statusBadgeClass(record.status)">
                  {{ statusLabel(record.status) }}
                </span>
                <button
                  @click="rebuildOperationRecord(record)"
                  :disabled="record.rebuilding || !record.project_slug || triggering"
                  class="rounded border border-blue-200 px-1.5 py-0.5 text-[11px] text-blue-600 hover:bg-blue-50 disabled:opacity-50"
                >
                  {{ record.rebuilding ? 'rebuild中' : 'rebuild' }}
                </button>
                <button
                  v-if="canCancelRecord(record)"
                  @click="cancelOperationRecord(record)"
                  :disabled="record.cancelling"
                  class="rounded border border-red-200 px-1.5 py-0.5 text-[11px] text-red-600 hover:bg-red-50 disabled:opacity-50"
                >
                  {{ record.cancelling ? '取消中' : '取消' }}
                </button>
              </div>
            </div>
            <div class="mt-1 flex items-center gap-1.5 text-xs text-gray-700 min-w-0">
              <span class="font-mono font-semibold truncate" :title="record.project_slug">{{ record.project_slug || '-' }}</span>
              <span class="text-[11px] text-gray-400 truncate" :title="record.project_name">{{ record.project_name }}</span>
              <a
                v-if="jenkinsPageUrl(record)"
                :href="jenkinsPageUrl(record)"
                target="_blank"
                rel="noopener noreferrer"
                class="ml-auto inline-flex shrink-0 items-center gap-0.5 text-[11px] text-blue-600 hover:text-blue-800 hover:underline"
                title="打开 Jenkins 发布页"
              >
                Jenkins
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="h-3 w-3">
                  <path fill-rule="evenodd" d="M4.25 5.5a.75.75 0 00-.75.75v8.5c0 .414.336.75.75.75h8.5a.75.75 0 00.75-.75v-4a.75.75 0 011.5 0v4A2.25 2.25 0 0112.75 17h-8.5A2.25 2.25 0 012 14.75v-8.5A2.25 2.25 0 014.25 4h5a.75.75 0 010 1.5h-5z" clip-rule="evenodd" />
                  <path fill-rule="evenodd" d="M6.194 12.753a.75.75 0 001.06.053L16.5 4.44v2.81a.75.75 0 001.5 0v-4.5a.75.75 0 00-.75-.75h-4.5a.75.75 0 000 1.5h2.553l-9.056 8.194a.75.75 0 00-.053 1.06z" clip-rule="evenodd" />
                </svg>
              </a>
            </div>
            <div class="mt-0.5 grid grid-cols-[3.25rem_minmax(0,1fr)] gap-1 text-[11px] text-gray-500">
              <span class="text-gray-400">project</span>
              <span class="font-mono truncate" :title="record.project_parameter">{{ record.project_parameter || '-' }}</span>
              <span class="text-gray-400">构建号</span>
              <a
                v-if="jenkinsBuildUrl(record)"
                :href="jenkinsBuildUrl(record)"
                target="_blank"
                rel="noopener noreferrer"
                class="font-mono truncate text-blue-600 hover:text-blue-800 hover:underline"
                :title="'打开 Jenkins 构建 #' + record.build_number"
              >#{{ record.build_number }}</a>
              <span v-else class="font-mono truncate">{{ record.build_number ? `#${record.build_number}` : '-' }}</span>
            </div>
            <div v-if="record.message" class="mt-1 text-[11px] text-gray-400 truncate" :title="record.message">
              {{ record.message }}
            </div>
          </div>
        </div>
      </aside>
    </div>
  </div>
</template>

<script>
let cachedBuildProjects = null;

const ParameterControl = {
  name: 'ParameterControl',
  props: {
    project: { type: Object, required: true },
    parameter: { type: Object, required: true },
    triggering: { type: Boolean, default: false }
  },
  emits: ['save', 'toggle-multi'],
  methods: {
    normalizedChoices(parameter) {
      return (parameter.choices || []).map((choice) => {
        if (choice && typeof choice === 'object') {
          return {
            value: choice.value,
            label: choice.label || choice.value
          };
        }

        return {
          value: choice,
          label: choice
        };
      });
    },
    isBooleanParameter(parameter) {
      return String(parameter.type || '').toLowerCase().includes('boolean');
    },
    isMultiSelected(project, parameter, value) {
      return (project.values[parameter.name] || []).includes(value);
    },
    formatDefault(value) {
      if (value === null || value === undefined || value === '') {
        return '-';
      }

      return String(value);
    }
  },
  template: `
    <label
      class="inline-flex items-center min-h-8 max-w-full rounded border border-gray-200 bg-white overflow-hidden"
      :class="{ 'min-h-[4.25rem]': parameter.name === 'project' }"
      :title="parameter.description || parameter.name"
    >
      <span class="self-stretch px-2 text-[11px] font-medium text-gray-500 bg-gray-50 border-r border-gray-200 inline-flex items-center whitespace-nowrap">
        {{ parameter.name }}
      </span>

      <select
        v-if="!parameter.multiple && parameter.choices && parameter.choices.length > 0"
        v-model="project.values[parameter.name]"
        :disabled="triggering"
        class="h-8 min-w-20 max-w-32 px-2 text-xs border-0 focus:ring-1 focus:ring-blue-500 disabled:bg-gray-100 disabled:text-gray-400"
        @change="$emit('save')"
      >
        <option v-for="choice in normalizedChoices(parameter)" :key="choice.value" :value="choice.value">{{ choice.label }}</option>
      </select>

                    <span v-else-if="parameter.multiple" class="px-2 py-1 grid grid-rows-2 grid-flow-col auto-cols-max gap-1.5 min-h-[4.25rem] max-w-full content-start overflow-x-auto">
        <label
          v-for="choice in normalizedChoices(parameter)"
          :key="choice.value"
          class="inline-flex items-center gap-1 rounded bg-gray-50 px-1.5 py-0.5 text-xs text-gray-700 border border-gray-200 cursor-pointer hover:bg-blue-50 hover:border-blue-200"
        >
          <input
            :checked="isMultiSelected(project, parameter, choice.value)"
            type="checkbox"
            :disabled="triggering"
            class="rounded border-gray-300 text-blue-600 focus:ring-blue-500 disabled:opacity-50"
            @change="$emit('toggle-multi', project, parameter.name, choice.value)"
          />
          {{ choice.label }}
        </label>
      </span>

      <span v-else-if="isBooleanParameter(parameter)" class="px-2 inline-flex items-center gap-1 text-xs text-gray-700">
        <input
          v-model="project.values[parameter.name]"
          type="checkbox"
          :disabled="triggering"
          class="rounded border-gray-300 text-blue-600 focus:ring-blue-500 disabled:opacity-50"
          @change="$emit('save')"
        />
      </span>

      <input
        v-else
        v-model="project.values[parameter.name]"
        type="text"
        :disabled="triggering"
        class="h-8 w-28 px-2 text-xs border-0 focus:ring-1 focus:ring-blue-500 disabled:bg-gray-100 disabled:text-gray-400"
        :placeholder="formatDefault(parameter.default)"
        @input="$emit('save')"
      />
    </label>
  `
};

export default {
  name: 'JenkinsBuilds',
  components: {
    ParameterControl
  },
  preferenceKey: 'toolbox.jenkinsBuilds.preferences.v1',
  cacheKey: 'toolbox.jenkinsBuilds.projectsCache.v2',
  operationRecordsKey: 'toolbox.jenkinsBuilds.operationRecords.v1',
  hiddenParameterNames: ['sql', 'masterCheck'],
  data() {
    return {
      loading: false,
      refreshing: false,
      triggering: false,
      statusChecking: false,
      statusPollingTimer: null,
      jenkinsHost: '',
      projects: [],
      operationRecords: [],
      primaryParameterOrder: ['env', 'branchName', 'deploy', 'deployVersion'],
      message: '',
      error: ''
    };
  },
  computed: {
    selectedProjects() {
      return this.projects.filter((project) => project.selected);
    },
    isAllSelected() {
      return this.projects.length > 0 && this.selectedProjects.length === this.projects.length;
    },
    runningOperationRecords() {
      return this.operationRecords.filter((record) => this.isRunningStatus(record.status));
    }
  },
  async mounted() {
    this.operationRecords = this.loadOperationRecords();
    this.loadProjects();
    this.ensureStatusPolling();
    this.checkOperationStatuses();
  },
  beforeUnmount() {
    this.stopStatusPolling();
  },
  methods: {
    loadProjects() {
      this.error = '';
      this.message = '';

      const freshCachedProjects = this.loadProjectsCache();
      if (freshCachedProjects) {
        cachedBuildProjects = freshCachedProjects;
        this.applyProjects(cachedBuildProjects);
      } else {
        if (!cachedBuildProjects) {
          cachedBuildProjects = this.loadProjectsCache({ allowExpired: true });
        }

        if (cachedBuildProjects) {
          this.applyProjects(cachedBuildProjects);
        } else {
          this.loading = true;
        }
      }

      this.refreshProjects({ silent: Boolean(cachedBuildProjects) });
    },
    async refreshProjects(options = {}) {
      if (this.refreshing) {
        return;
      }

      const { silent = false } = options;
      this.refreshing = true;
      this.error = '';

      if (!silent && this.projects.length === 0) {
        this.loading = true;
      }

      try {
        const response = await fetch('/api/admin/jenkins/build-projects', {
          headers: { Accept: 'application/json' }
        });
        const data = await response.json();

        if (!response.ok || !data.success) {
          this.error = data.message || '加载 Jenkins 项目失败';
          return;
        }

        cachedBuildProjects = data.data.projects || [];
        this.jenkinsHost = data.data.jenkins_host || this.jenkinsHost;
        this.saveProjectsCache(cachedBuildProjects);
        this.applyProjects(cachedBuildProjects, { preserveCurrentValues: true });

        if (!silent) {
          this.message = 'Jenkins 项目已刷新';
        }
      } catch (error) {
        this.error = error.message;
      } finally {
        this.loading = false;
        this.refreshing = false;
      }
    },
    applyProjects(projects, options = {}) {
      const { preserveCurrentValues = false } = options;
      const preferences = this.loadPreferences();
      const currentProjects = new Map(this.projects.map((project) => [this.preferenceProjectKey(project), project]));

      this.projects = projects.map((project) => {
        const parameters = this.visibleParameters(project.parameters || []);
        const defaults = this.defaultValues(parameters);
        const key = this.preferenceProjectKey(project);
        const saved = preferences[key] || {};
        const current = preserveCurrentValues ? currentProjects.get(key) : null;

        return {
          ...project,
          selected: current ? Boolean(current.selected) : Boolean(saved.selected),
          parameters,
          values: this.mergeSavedValues(defaults, current?.values || saved.values || {}, parameters)
        };
      });

      this.savePreferences();
    },
    loadProjectsCache(options = {}) {
      try {
        const { allowExpired = false } = options;
        const cache = JSON.parse(window.localStorage.getItem(this.$options.cacheKey) || 'null');
        if (!cache || !Array.isArray(cache.projects)) {
          return null;
        }

        if (!allowExpired && cache.date !== this.todayKey()) {
          return null;
        }

        if (cache.jenkins_host) {
          this.jenkinsHost = cache.jenkins_host;
        }

        return cache.projects;
      } catch (error) {
        return null;
      }
    },
    saveProjectsCache(projects) {
      window.localStorage.setItem(this.$options.cacheKey, JSON.stringify({
        date: this.todayKey(),
        jenkins_host: this.jenkinsHost,
        projects
      }));
    },
    todayKey() {
      const date = new Date();
      const pad = (value) => String(value).padStart(2, '0');

      return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
    },
    defaultValues(parameters) {
      return parameters.reduce((values, parameter) => {
        if (parameter.multiple) {
          values[parameter.name] = Array.isArray(parameter.default) ? parameter.default : [];
        } else if (parameter.default !== null && parameter.default !== undefined) {
          values[parameter.name] = parameter.default;
        } else if (parameter.choices && parameter.choices.length > 0) {
          values[parameter.name] = this.normalizedChoices(parameter)[0]?.value || '';
        } else if (this.isBooleanParameter(parameter)) {
          values[parameter.name] = false;
        } else {
          values[parameter.name] = '';
        }

        return values;
      }, {});
    },
    visibleParameters(parameters) {
      return parameters.filter((parameter) => !this.$options.hiddenParameterNames.includes(parameter.name));
    },
    mergeSavedValues(defaults, savedValues, parameters) {
      const merged = { ...defaults };
      const parameterNames = new Set(parameters.map((parameter) => parameter.name));

      Object.entries(savedValues).forEach(([name, value]) => {
        if (!parameterNames.has(name)) {
          return;
        }

        merged[name] = Array.isArray(defaults[name]) ? (Array.isArray(value) ? value : []) : value;
      });

      return merged;
    },
    loadPreferences() {
      try {
        return JSON.parse(window.localStorage.getItem(this.$options.preferenceKey) || '{}');
      } catch (error) {
        return {};
      }
    },
    loadOperationRecords() {
      try {
        const records = JSON.parse(window.localStorage.getItem(this.$options.operationRecordsKey) || '[]');
        return Array.isArray(records) ? records.map((record) => this.normalizeOperationRecord(record)) : [];
      } catch (error) {
        return [];
      }
    },
    saveOperationRecords() {
      window.localStorage.setItem(this.$options.operationRecordsKey, JSON.stringify(this.operationRecords.slice(0, 30)));
    },
    savePreferences() {
      const preferences = this.projects.reduce((payload, project) => {
        payload[this.preferenceProjectKey(project)] = {
          selected: Boolean(project.selected),
          values: project.values || {}
        };

        return payload;
      }, {});

      window.localStorage.setItem(this.$options.preferenceKey, JSON.stringify(preferences));
    },
    preferenceProjectKey(project) {
      return project.jenkins_job_name || project.slug;
    },
    jenkinsPageUrl(item) {
      if (!item) {
        return null;
      }

      if (item.jenkins_job_url) {
        return item.jenkins_job_url;
      }

      return this.buildJenkinsUrl(item.jenkins_job_name || item.job_name);
    },
    jenkinsBuildUrl(record) {
      if (!record?.build_number) {
        return null;
      }

      if (record.build_url) {
        return record.build_url;
      }

      return this.buildJenkinsUrl(record.job_name || record.jenkins_job_name, record.build_number);
    },
    buildJenkinsUrl(jobName, buildNumber = null) {
      if (!jobName) {
        return null;
      }

      const segments = String(jobName).split('/').map((segment) => segment.trim()).filter(Boolean);
      if (segments.length === 0) {
        return null;
      }

      const host = (this.jenkinsHost || '').replace(/\/$/, '');
      if (!host) {
        return null;
      }

      const path = '/job/' + segments.map(encodeURIComponent).join('/job/') + '/';
      return buildNumber ? `${host}${path}${buildNumber}/` : `${host}${path}`;
    },
    isBooleanParameter(parameter) {
      return String(parameter.type || '').toLowerCase().includes('boolean');
    },
    normalizedChoices(parameter) {
      return (parameter.choices || []).map((choice) => {
        if (choice && typeof choice === 'object') {
          return {
            value: choice.value,
            label: choice.label || choice.value,
            selected: Boolean(choice.selected)
          };
        }

        return {
          value: choice,
          label: choice,
          selected: false
        };
      });
    },
    isMultiSelected(project, parameter, value) {
      return (project.values[parameter.name] || []).includes(value);
    },
    hasSecondaryParameters(project) {
      return Boolean(this.parameterByName(project, 'region') || this.parameterByName(project, 'project'));
    },
    parameterByName(project, name) {
      return project.parameters.find((parameter) => parameter.name === name);
    },
    toggleMultiValue(project, parameterName, value) {
      const current = project.values[parameterName] || [];
      if (current.includes(value)) {
        project.values[parameterName] = current.filter((item) => item !== value);
        this.savePreferences();
        return;
      }

      project.values[parameterName] = [...current, value];
      this.savePreferences();
    },
    formatDefault(value) {
      if (value === null || value === undefined || value === '') {
        return '-';
      }

      return String(value);
    },
    toggleAll() {
      const nextValue = !this.isAllSelected;
      this.projects.forEach((project) => {
        project.selected = nextValue;
      });
      this.savePreferences();
    },
    async triggerBuilds() {
      if (this.selectedProjects.length === 0) {
        this.error = '请至少选择一个项目';
        return;
      }

      if (!window.confirm(`确认触发 ${this.selectedProjects.length} 个 Jenkins Build 吗？`)) {
        return;
      }

      this.triggering = true;
      this.error = '';
      this.message = '';
      const requestedBuilds = this.selectedProjects.map((project) => ({
        project_slug: project.slug,
        project_name: project.name,
        job_name: project.jenkins_job_name,
        parameters: this.serializeBuildParameters(project.values)
      }));

      try {
        const { response, data } = await this.submitBuilds(requestedBuilds);
        if (!response.ok || !data.success) {
          this.error = data.message || '触发失败';
          return;
        }

        this.message = data.message || '触发成功';
      } catch (error) {
        this.error = error.message;
      } finally {
        this.triggering = false;
      }
    },
    async submitBuilds(requestedBuilds) {
      const response = await fetch('/api/admin/jenkins/trigger-builds', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          Accept: 'application/json'
        },
        body: JSON.stringify({
          builds: requestedBuilds.map((build) => ({
            project_slug: build.project_slug,
            parameters: build.parameters
          }))
        })
      });
      const data = await response.json();
      this.addOperationRecords(data.data?.results || [], requestedBuilds);

      return { response, data };
    },
    async rebuildOperationRecord(record) {
      if (!record.project_slug) {
        this.error = '缺少项目标识，无法 rebuild';
        return;
      }

      if (!window.confirm(`确认 rebuild ${record.project_slug} 吗？`)) {
        return;
      }

      this.operationRecords = this.operationRecords.map((item) => (
        item.id === record.id ? { ...item, rebuilding: true } : item
      ));
      this.saveOperationRecords();
      this.error = '';
      this.message = '';

      try {
        const { response, data } = await this.submitBuilds([{
          project_slug: record.project_slug,
          project_name: record.project_name,
          job_name: record.job_name,
          parameters: record.parameters || {}
        }]);

        if (!response.ok || !data.success) {
          this.error = data.message || 'rebuild 失败';
          return;
        }

        this.message = data.message || 'rebuild 已触发';
      } catch (error) {
        this.error = error.message;
      } finally {
        this.operationRecords = this.operationRecords.map((item) => (
          item.id === record.id ? { ...item, rebuilding: false } : item
        ));
        this.saveOperationRecords();
      }
    },
    serializeBuildParameters(values) {
      return Object.entries(values || {}).reduce((payload, [name, value]) => {
        if (this.$options.hiddenParameterNames.includes(name)) {
          return payload;
        }

        if (Array.isArray(value)) {
          payload[name] = value.filter((item) => item !== null && item !== '').join(',');
        } else {
          payload[name] = value;
        }

        return payload;
      }, {});
    },
    addOperationRecords(results, requestedBuilds) {
      const resultList = Array.isArray(results) ? results : [];
      const requestedBySlug = new Map(requestedBuilds.map((build) => [build.project_slug, build]));
      const records = resultList.map((result) => {
        const requested = requestedBySlug.get(result.project_slug) || {};
        const canTrackBuild = Boolean(result.queue_url || result.build_number);

        return {
          id: `${Date.now()}-${result.project_slug || Math.random().toString(36).slice(2, 8)}`,
          time: this.formatRecordTime(new Date()),
          project_slug: result.project_slug || requested.project_slug || '',
          project_name: result.project_name || requested.project_name || '',
          job_name: result.job_name || requested.job_name || '',
          jenkins_job_url: result.jenkins_job_url || this.buildJenkinsUrl(result.job_name || requested.job_name),
          project_parameter: this.formatParameterValue(requested.parameters?.project),
          status: result.success ? (canTrackBuild ? 'PENDING' : 'UNKNOWN') : 'FAILURE',
          queue_url: result.queue_url || null,
          build_number: result.build_number || null,
          build_url: result.build_url || (result.build_number
            ? this.buildJenkinsUrl(result.job_name || requested.job_name, result.build_number)
            : null),
          parameters: requested.parameters || {},
          message: result.success
            ? (canTrackBuild ? '已提交 Jenkins，等待发布结果' : '已提交 Jenkins，但未返回队列地址，无法自动跟踪或取消')
            : (result.message || '触发失败'),
          cancelling: false,
          rebuilding: false
        };
      });

      this.operationRecords = [
        ...records,
        ...this.operationRecords
      ].slice(0, 30);

      this.saveOperationRecords();
      this.ensureStatusPolling();
      this.checkOperationStatuses();
    },
    normalizeOperationRecord(record) {
      if (record.status) {
        return {
          ...record,
          parameters: record.parameters || {},
          cancelling: false,
          rebuilding: false
        };
      }

      return {
        id: record.id || `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
        time: record.time || '-',
        project_slug: record.projects || '',
        project_name: record.projects || '历史发布记录',
        job_name: '',
        project_parameter: '-',
        status: record.failed > 0 ? 'FAILURE' : 'SUCCESS',
        queue_url: null,
        build_number: null,
        build_url: null,
        parameters: {},
        message: `${record.success || 0} 成功 / ${record.failed || 0} 失败`,
        cancelling: false,
        rebuilding: false
      };
    },
    async checkOperationStatuses() {
      const runningRecords = this.runningOperationRecords.filter((record) => record.queue_url || record.build_number);
      if (this.statusChecking || runningRecords.length === 0) {
        this.ensureStatusPolling();
        return;
      }

      this.statusChecking = true;

      try {
        const response = await fetch('/api/admin/jenkins/build-statuses', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            Accept: 'application/json'
          },
          body: JSON.stringify({
            builds: runningRecords.map((record) => ({
              id: record.id,
              project_slug: record.project_slug,
              queue_url: record.queue_url,
              build_number: record.build_number
            }))
          })
        });
        const data = await response.json();

        if (!response.ok || !data.success) {
          this.error = data.message || '查询 Jenkins 发布状态失败';
          return;
        }

        const statuses = new Map((data.data?.results || []).map((result) => [result.id, result]));
        this.operationRecords = this.operationRecords.map((record) => {
          const status = statuses.get(record.id);
          if (!status) {
            return record;
          }

          const nextStatus = record.status === 'CANCELING' && !status.completed
            ? 'CANCELING'
            : this.normalizeJenkinsStatus(status);

          return {
            ...record,
            status: nextStatus,
            build_number: status.build_number || record.build_number,
            build_url: status.build_url || record.build_url,
            jenkins_job_url: status.jenkins_job_url || record.jenkins_job_url,
            message: status.message || null,
            cancelling: false
          };
        });

        this.saveOperationRecords();
      } catch (error) {
        this.error = error.message;
      } finally {
        this.statusChecking = false;
        this.ensureStatusPolling();
      }
    },
    async cancelOperationRecord(record) {
      if (!window.confirm(`确认取消 ${record.project_slug} 的 Jenkins 发布吗？`)) {
        return;
      }

      this.operationRecords = this.operationRecords.map((item) => (
        item.id === record.id ? { ...item, cancelling: true, status: 'CANCELING', message: '正在发送取消请求' } : item
      ));
      this.saveOperationRecords();

      try {
        const response = await fetch('/api/admin/jenkins/cancel-build', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            Accept: 'application/json'
          },
          body: JSON.stringify({
            project_slug: record.project_slug,
            queue_url: record.queue_url,
            build_number: record.build_number
          })
        });
        const data = await response.json();

        if (!response.ok || !data.success) {
          this.error = data.message || '取消 Jenkins 发布失败';
          this.operationRecords = this.operationRecords.map((item) => (
            item.id === record.id ? { ...item, cancelling: false, status: record.status, message: data.message || item.message } : item
          ));
          this.saveOperationRecords();
          return;
        }

        const cancelResult = data.data?.result || {};
        const nextStatus = cancelResult.cancelled_queue ? 'ABORTED' : 'CANCELING';
        const nextMessage = cancelResult.cancelled_queue ? '已取消 Jenkins 队列任务' : '已发送停止请求，等待 Jenkins 确认';
        this.operationRecords = this.operationRecords.map((item) => (
          item.id === record.id ? { ...item, cancelling: false, status: nextStatus, message: nextMessage } : item
        ));
        this.saveOperationRecords();
        this.ensureStatusPolling();
        if (nextStatus === 'CANCELING') {
          this.checkOperationStatuses();
        }
      } catch (error) {
        this.error = error.message;
        this.operationRecords = this.operationRecords.map((item) => (
          item.id === record.id ? { ...item, cancelling: false, status: record.status, message: error.message } : item
        ));
        this.saveOperationRecords();
      }
    },
    normalizeJenkinsStatus(status) {
      if (!status.success && status.status === 'UNKNOWN') {
        return 'UNKNOWN';
      }

      if (status.completed) {
        return status.status || status.result || 'UNKNOWN';
      }

      return status.status === 'PENDING' ? 'PENDING' : 'BUILDING';
    },
    ensureStatusPolling() {
      const hasPollableRecords = this.runningOperationRecords.some((record) => record.queue_url || record.build_number);
      if (!hasPollableRecords) {
        this.stopStatusPolling();
        return;
      }

      if (!this.statusPollingTimer) {
        this.statusPollingTimer = window.setInterval(() => {
          this.checkOperationStatuses();
        }, 10000);
      }
    },
    stopStatusPolling() {
      if (this.statusPollingTimer) {
        window.clearInterval(this.statusPollingTimer);
        this.statusPollingTimer = null;
      }
    },
    isRunningStatus(status) {
      return ['PENDING', 'BUILDING', 'CANCELING'].includes(status);
    },
    canCancelRecord(record) {
      return ['PENDING', 'BUILDING'].includes(record.status)
        && Boolean(record.queue_url || record.build_number)
        && !record.cancelling;
    },
    statusLabel(status) {
      return {
        PENDING: '发布中',
        BUILDING: '发布中',
        CANCELING: '取消中',
        SUCCESS: '成功',
        FAILURE: '失败',
        ABORTED: '已取消',
        UNSTABLE: '不稳定',
        UNKNOWN: '未知'
      }[status] || '未知';
    },
    statusBadgeClass(status) {
      if (['PENDING', 'BUILDING', 'CANCELING'].includes(status)) {
        return 'bg-blue-50 text-blue-600';
      }

      if (status === 'SUCCESS') {
        return 'bg-green-50 text-green-600';
      }

      if (status === 'UNSTABLE') {
        return 'bg-yellow-50 text-yellow-700';
      }

      return 'bg-red-50 text-red-600';
    },
    formatParameterValue(value) {
      if (Array.isArray(value)) {
        return value.join(', ');
      }

      if (value === null || value === undefined || value === '') {
        return '-';
      }

      return String(value);
    },
    formatRecordTime(date) {
      const pad = (value) => String(value).padStart(2, '0');

      return `${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
    }
  }
}
</script>
