const STORAGE_KEY = 'oem-batch-builder-jobs-v3';
const LEGACY_KEYS = ['oem-batch-builder-jobs-v2', 'oem-batch-builder-jobs-v1'];
const defaultApiBaseUrl = 'http://47.107.86.179:3002/api';
const defaultVersion = '4.5.34';

let jobs = [];
let running = false;
let statusPollTimer = null;
let logLines = [];
let logFlushScheduled = false;
const MAX_LOG_LINES = 180;
const MAX_LOG_MESSAGE_LENGTH = 1000;
const FULL_PACKAGE_COMMAND = 'npm run build:win';

const grid = document.getElementById('jobGrid');
const template = document.getElementById('jobTemplate');
const logBox = document.getElementById('logBox');
const outputDirText = document.getElementById('outputDir');
const runBtn = document.getElementById('runBtn');
const cancelBtn = document.getElementById('cancelBtn');

function diagnostic(message, payload = {}) {
  try {
    if (window.oemTool && typeof window.oemTool.diagnostic === 'function') {
      window.oemTool.diagnostic(message, payload).catch(() => {});
    }
  } catch {
    // ignore diagnostics failure
  }
}

window.addEventListener('error', event => {
  diagnostic('window error', {
    message: event.message,
    filename: event.filename,
    lineno: event.lineno,
    colno: event.colno,
    stack: event.error && event.error.stack ? event.error.stack : '',
  });
});

window.addEventListener('unhandledrejection', event => {
  const reason = event.reason;
  diagnostic('unhandled rejection', {
    message: reason && reason.message ? reason.message : String(reason),
    stack: reason && reason.stack ? reason.stack : '',
  });
});

function createJob(seed = {}) {
  const seedAssets = seed.assets || {};
  const buildCommand = normalizeBuildCommand(seed.buildCommand);
  const isSmokeDraft = /^ui-smoke-|^ui-buildwin-/.test(String(seed.id || ''));
  return {
    id: isSmokeDraft || !seed.id ? `oem-${Date.now()}-${Math.random().toString(36).slice(2, 8)}` : seed.id,
    enabled: seed.enabled !== false,
    brandName: seed.brandName || '',
    brandSlogan: seed.brandSlogan || 'One-stop AI digital human system',
    brandShortName: seed.brandShortName || '',
    brandDisplayName: seed.brandDisplayName || '',
    apiBaseUrl: seed.apiBaseUrl || defaultApiBaseUrl,
    version: seed.version || defaultVersion,
    buildCommand,
    assets: {
      qrcodePath: seedAssets.qrcodePath || '',
      logoPath: seedAssets.logoPath || '',
      iconPath: seedAssets.iconPath || '',
      loginBgPath: seedAssets.loginBgPath || '',
    },
    status: isSmokeDraft ? 'idle' : (seed.status || 'idle'),
    message: isSmokeDraft ? '' : (seed.message || ''),
  };
}

function normalizeBuildCommand(command) {
  const trimmed = String(command || '').trim();
  if (!trimmed) return FULL_PACKAGE_COMMAND;

  // Older smoke tests wrote plain npm run build into the shared Electron profile.
  // The OEM tool is for producing packages, so repair that polluted draft value.
  if (/^npm(?:\.cmd)?\s+run\s+build$/i.test(trimmed)) {
    return FULL_PACKAGE_COMMAND;
  }
  return trimmed;
}

function saveDrafts() {
  localStorage.setItem(STORAGE_KEY, JSON.stringify(jobs));
}

function loadDrafts() {
  try {
    let raw = localStorage.getItem(STORAGE_KEY);
    for (const key of LEGACY_KEYS) {
      if (!raw) raw = localStorage.getItem(key);
    }
    const parsed = raw ? JSON.parse(raw) : null;
    jobs = Array.isArray(parsed) && parsed.length > 0 ? parsed.map(createJob) : [createJob()];
    jobs.forEach(repairJobPaths);
    saveDrafts();
  } catch {
    jobs = [createJob()];
    saveDrafts();
  }
}

function fileName(filePath) {
  return String(filePath || '').split(/[\\/]/).pop() || '';
}

function repairKnownMojibakePath(filePath) {
  return String(filePath || '')
    .replace(/cursor澶囦唤/g, 'cursor备份')
    .replace(/cursor锟斤拷锟斤拷/g, 'cursor备份');
}

function repairJobPaths(job) {
  for (const key of ['qrcodePath', 'logoPath', 'iconPath', 'loginBgPath']) {
    job.assets[key] = repairKnownMojibakePath(job.assets[key]);
  }
}

function isSupportedImage(filePath) {
  return /\.(png|jpe?g|webp|ico|icns)$/i.test(String(filePath || ''));
}

function normalizeShortName(job) {
  if (!job.brandShortName.trim()) {
    job.brandShortName = (job.brandName || 'OEMApp')
      .replace(/\s+/g, '-')
      .replace(/[^\w.-]/g, '')
      .slice(0, 48) || 'OEMApp';
  }
  if (!job.brandDisplayName.trim()) {
    job.brandDisplayName = job.brandName;
  }
}

function validateJob(job) {
  normalizeShortName(job);
  if (!job.brandName.trim()) return 'Software name is required';
  if (!job.apiBaseUrl.trim()) return 'API base URL is required';
  if (!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(job.version)) return 'Version must be x.y.z';
  if (!job.assets.qrcodePath) return 'QR code image is missing';
  if (!job.assets.logoPath) return 'Logo image is missing';
  if (!job.assets.iconPath) return 'Icon image is missing';
  if (!job.assets.loginBgPath) return 'Login background image is missing';
  return '';
}

function appendLog(payload) {
  const prefix = payload.jobId ? `[${payload.jobId}]` : '[batch]';
  let message = String(payload.message || '').trimEnd();
  if (!message) return;
  if (message.length > MAX_LOG_MESSAGE_LENGTH) {
    message = `${message.slice(0, MAX_LOG_MESSAGE_LENGTH)}\n... [log chunk truncated]`;
  }
  const stamp = new Date(payload.time || Date.now()).toLocaleTimeString();
  for (const line of message.split(/\r?\n/)) {
    logLines.push(`${stamp} ${prefix} ${line}`);
  }
  if (logLines.length > MAX_LOG_LINES) {
    logLines = logLines.slice(-MAX_LOG_LINES);
  }
  if (logFlushScheduled) return;
  logFlushScheduled = true;
  requestAnimationFrame(() => {
    logFlushScheduled = false;
    logBox.textContent = `${logLines.join('\n')}\n`;
    logBox.scrollTop = logBox.scrollHeight;
  });
}

function updateRunState() {
  const enabledCount = jobs.filter(job => job.enabled).length;
  runBtn.disabled = running;
  cancelBtn.disabled = !running;
  runBtn.textContent = running ? 'Packaging...' : `OEM + Package (${enabledCount})`;
}

function applyResults(result) {
  if (result && result.outputDir) outputDirText.textContent = result.outputDir;
  const resultMap = new Map((result && result.results || []).map(item => [item.id, item]));
  jobs.forEach(job => {
    const item = resultMap.get(job.id);
    if (!item) return;
    job.status = item.success ? 'success' : 'failed';
    job.message = item.message || (item.success ? 'Done' : 'Failed');
  });
  saveDrafts();
  render();
}

function stopStatusPolling() {
  if (statusPollTimer) {
    clearInterval(statusPollTimer);
    statusPollTimer = null;
  }
}

function startStatusPolling() {
  stopStatusPolling();
  statusPollTimer = setInterval(async () => {
    try {
      const status = await window.oemTool.getStatus();
      if (status && status.outputDir) outputDirText.textContent = status.outputDir;
      running = Boolean(status && status.running);
      if (status && Array.isArray(status.results) && status.results.length > 0) {
        applyResults(status);
      }
      if (!running) stopStatusPolling();
      updateRunState();
    } catch (error) {
      appendLog({ message: error && error.message ? error.message : String(error) });
    }
  }, 1200);
}

function setAsset(job, key, filePath) {
  if (!filePath) return;
  if (!isSupportedImage(filePath)) {
    appendLog({ jobId: job.id, message: `Unsupported image type: ${filePath}` });
    return;
  }
  job.assets[key] = filePath;
  job.status = 'idle';
  job.message = '';
  saveDrafts();
  render();
}

function renderAssetButton(button, job, key) {
  const img = button.querySelector('img');
  const small = button.querySelector('small');
  const filePath = job.assets[key];

  button.classList.toggle('filled', Boolean(filePath));
  button.title = filePath || 'Click to select or drag an image here';

  if (filePath) {
    img.src = window.oemTool.pathToFileUrl(filePath);
    img.style.display = 'block';
    small.textContent = fileName(filePath);
  } else {
    img.removeAttribute('src');
    img.style.display = 'none';
  }
}

function render() {
  grid.innerHTML = '';

  jobs.forEach((job, index) => {
    const node = template.content.firstElementChild.cloneNode(true);
    node.classList.add(job.status);
    node.querySelector('.index').textContent = `#${index + 1}`;
    node.querySelector('.status').textContent = job.status;
    node.querySelector('.message').textContent = job.message || '';

    node.querySelectorAll('[data-field]').forEach(input => {
      const field = input.dataset.field;
      if (input.type === 'checkbox') {
        input.checked = Boolean(job[field]);
      } else {
        input.value = job[field] || '';
      }

      input.addEventListener('change', () => {
        job[field] = input.type === 'checkbox' ? input.checked : input.value;
        if (job.status === 'success' || job.status === 'failed') job.status = 'idle';
        saveDrafts();
        render();
      });
    });

    node.querySelectorAll('[data-asset]').forEach(button => {
      const key = button.dataset.asset;
      renderAssetButton(button, job, key);

      button.addEventListener('click', async () => {
        const filePath = await window.oemTool.pickImage();
        setAsset(job, key, filePath);
      });

      button.addEventListener('dragover', event => {
        event.preventDefault();
        button.classList.add('dragging');
      });
      button.addEventListener('dragleave', () => {
        button.classList.remove('dragging');
      });
      button.addEventListener('drop', event => {
        event.preventDefault();
        button.classList.remove('dragging');
        const file = event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0];
        setAsset(job, key, file && file.path ? file.path : '');
      });
    });

    node.querySelector('[data-action="copy"]').addEventListener('click', () => {
      jobs.push(createJob({
        ...job,
        id: undefined,
        brandName: `${job.brandName || 'OEM'} Copy`,
        status: 'idle',
        message: '',
      }));
      saveDrafts();
      render();
    });

    node.querySelector('[data-action="delete"]').addEventListener('click', () => {
      jobs = jobs.filter(item => item.id !== job.id);
      if (jobs.length === 0) jobs.push(createJob());
      saveDrafts();
      render();
    });

    grid.appendChild(node);
  });

  updateRunState();
}

async function runBatch() {
  diagnostic('runBatch clicked', { running, jobs: jobs.length, enabled: jobs.filter(job => job.enabled).length });
  if (running) return;

  const activeJobs = jobs.filter(job => job.enabled);
  if (activeJobs.length === 0) {
    appendLog({ message: 'Select at least one OEM job' });
    return;
  }

  for (const job of activeJobs) {
    const error = validateJob(job);
    if (error) {
      job.status = 'failed';
      job.message = error;
      saveDrafts();
      render();
      appendLog({ jobId: job.id, message: `${job.brandName || job.id}: ${error}` });
      return;
    }
  }

  logBox.textContent = '';
  logLines = [];
  running = true;
  activeJobs.forEach(job => {
    job.status = 'running';
    job.message = 'Starting background package task';
  });
  saveDrafts();
  render();

  try {
    diagnostic('runBatch invoking ipc', { activeJobs: activeJobs.length });
    const result = await window.oemTool.runBatch(activeJobs.map(job => ({
      id: job.id,
      brandName: job.brandName,
      brandSlogan: job.brandSlogan,
      brandShortName: job.brandShortName,
      brandDisplayName: job.brandDisplayName,
      apiBaseUrl: job.apiBaseUrl,
      version: job.version,
      assets: job.assets,
      buildCommand: normalizeBuildCommand(job.buildCommand),
    })));
    diagnostic('runBatch ipc resolved', {
      accepted: Boolean(result && result.accepted),
      running: Boolean(result && result.running),
      message: result && result.message ? result.message : '',
    });

    if (result && result.outputDir) outputDirText.textContent = result.outputDir;
    if (result && result.accepted) {
      appendLog({ message: result.message || 'Build started in background' });
      activeJobs.forEach(job => {
        job.status = 'running';
        job.message = 'Running in background';
      });
      saveDrafts();
      render();
      startStatusPolling();
      return;
    }

    running = false;
    applyResults(result || {});
  } catch (error) {
    diagnostic('runBatch ipc rejected', {
      message: error && error.message ? error.message : String(error),
      stack: error && error.stack ? error.stack : '',
    });
    appendLog({ message: error && error.message ? error.message : String(error) });
    activeJobs.forEach(job => {
      if (job.status === 'running') {
        job.status = 'failed';
        job.message = 'Package failed';
      }
    });
    running = false;
    saveDrafts();
    render();
  }
}

document.addEventListener('dragover', event => event.preventDefault());
document.addEventListener('drop', event => event.preventDefault());

document.getElementById('addJobBtn').addEventListener('click', () => {
  jobs.push(createJob());
  saveDrafts();
  render();
});

document.getElementById('openOutputBtn').addEventListener('click', async () => {
  const result = await window.oemTool.openOutputDir();
  if (result && result.outputDir) outputDirText.textContent = result.outputDir;
});

cancelBtn.addEventListener('click', async () => {
  await window.oemTool.cancel();
  running = false;
  stopStatusPolling();
  updateRunState();
});

runBtn.addEventListener('click', runBatch);
window.oemTool.onLog(appendLog);

loadDrafts();
render();
window.oemTool.getStatus().then(status => {
  if (status && status.outputDir) outputDirText.textContent = status.outputDir;
  running = Boolean(status && status.running);
  if (running) {
    jobs.forEach(job => {
      if (job.enabled && job.status !== 'success' && job.status !== 'failed') {
        job.status = 'running';
        job.message = 'Running in background';
      }
    });
    startStatusPolling();
  } else if (status && Array.isArray(status.results) && status.results.length > 0) {
    applyResults(status);
  }
  updateRunState();
}).catch(error => appendLog({ message: error && error.message ? error.message : String(error) }));
