import type { DailyMileageRow } from '../../api/types';
import { downloadBlob } from './download';

export type MileageExportVehicle = { vin: string; plate: string };
export type MileageExportSource = { protocol: string; label: string; mileageType: string };
export type MileageWorkbookRow = Pick<DailyMileageRow, 'vin' | 'date' | 'dailyMileageKm'> & Partial<Omit<DailyMileageRow, 'vin' | 'date' | 'dailyMileageKm'>>;

export type MileageExportInput = {
  dateFrom: string;
  dateTo: string;
  dates: string[];
  vehicles: MileageExportVehicle[];
  mileageRows: MileageWorkbookRow[];
  sources: MileageExportSource[];
  exportedAt?: Date;
};

type MileageWorkerResponse = { ok: true; buffer: ArrayBuffer } | { ok: false; message: string };
type MileageWorkerRequest =
  | { type: 'start'; input: Omit<MileageExportInput, 'vehicles' | 'mileageRows'> }
  | { type: 'rows'; rows: MileageWorkbookRow[] }
  | { type: 'finish'; vehicles: MileageExportVehicle[] };

function abortError() {
  return new DOMException('里程导出已取消', 'AbortError');
}

function throwIfAborted(signal?: AbortSignal) {
  if (signal?.aborted) throw abortError();
}

export type MileageExportStream = {
  appendRows: (rows: readonly MileageWorkbookRow[]) => void;
  finish: (vehicles: MileageExportVehicle[]) => Promise<void>;
  dispose: () => void;
};

export function createMileageExportStream(input: Omit<MileageExportInput, 'vehicles' | 'mileageRows'>, signal?: AbortSignal): MileageExportStream {
  throwIfAborted(signal);
  if (typeof Worker === 'undefined') {
    throw new Error('当前浏览器不支持后台 Excel 导出，请升级到最新版 Chrome、Edge 或 Safari 后重试');
  }

  const worker = new Worker(new URL('./mileageExport.worker.ts', import.meta.url), { type: 'module' });
  let disposed = false;
  let finishing = false;
  let resolveFinish: (() => void) | undefined;
  let rejectFinish: ((reason?: unknown) => void) | undefined;
  let earlyError: Error | undefined;
  const dispose = () => {
    if (disposed) return;
    disposed = true;
    signal?.removeEventListener('abort', cancel);
    worker.terminate();
  };
  const reject = (error: Error) => {
    if (disposed) return;
    if (rejectFinish) rejectFinish(error);
    else earlyError = error;
    dispose();
  };
  const cancel = () => reject(abortError());
  worker.onmessage = (event: MessageEvent<MileageWorkerResponse>) => {
    if (disposed) return;
    if (!event.data.ok) {
      reject(new Error(event.data.message));
      return;
    }
    const buffer = event.data.buffer;
    const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    downloadBlob(blob, `车辆里程查询_${input.dateFrom.split('-').join('')}-${input.dateTo.split('-').join('')}_${finishVehicles.length}辆.xlsx`);
    resolveFinish?.();
    dispose();
  };
  worker.onerror = (event) => reject(new Error(event.message || 'Excel 工作线程执行失败'));
  signal?.addEventListener('abort', cancel, { once: true });
  let finishVehicles: MileageExportVehicle[] = [];
  const post = (message: MileageWorkerRequest) => worker.postMessage(message);
  post({ type: 'start', input });

  return {
    appendRows(rows) {
      if (disposed || finishing) return;
      post({ type: 'rows', rows: rows.map((row) => ({ vin: row.vin, date: row.date, dailyMileageKm: row.dailyMileageKm })) });
    },
    finish(vehicles) {
      if (disposed) return Promise.reject(earlyError ?? abortError());
      if (finishing) return Promise.reject(new Error('里程导出已经进入生成阶段'));
      finishing = true;
      finishVehicles = vehicles;
      return new Promise<void>((resolve, rejectPromise) => {
        resolveFinish = resolve;
        rejectFinish = rejectPromise;
        if (earlyError) {
          rejectPromise(earlyError);
          dispose();
          return;
        }
        post({ type: 'finish', vehicles });
      });
    },
    dispose
  };
}

export async function downloadMileageWorkbook(input: MileageExportInput, signal?: AbortSignal) {
  const stream = createMileageExportStream({
    dateFrom: input.dateFrom,
    dateTo: input.dateTo,
    dates: input.dates,
    sources: input.sources,
    exportedAt: input.exportedAt
  }, signal);
  stream.appendRows(input.mileageRows);
  await stream.finish(input.vehicles);
}
