import {
  exportReportBlob,
  exportReportFileName,
  type ExportReport,
} from './export-report';
import { saveBlob } from './save-result';
import { safeExportFileName } from './output-name';
import { type ExportResult, ExportResultCollection } from './result-collection';
import {
  createResultZip,
  resultEntries,
  type CreateResultZipOptions,
  type ResultZip,
} from './zip-results';

export type SaveOutcome = 'saved' | 'downloaded' | 'cancelled';

export type BlobSaver = (
  blob: Blob,
  fileName: string,
  mimeType?: string
) => Promise<SaveOutcome>;

export interface SaveExportResultOptions {
  readonly saver?: BlobSaver;
  readonly fileName?: string;
}

export interface SaveCollectionResultOptions extends SaveExportResultOptions {
  /**
   * Defaults to true so large result Blobs and their preview URLs are released
   * after a successful save or download.
   */
  readonly releaseAfterSave?: boolean;
}

export interface SaveResultsZipOptions extends Omit<
  CreateResultZipOptions,
  'fileName'
> {
  readonly fileName?: string;
  readonly report?: ExportReport;
  readonly saver?: BlobSaver;
}

export interface SavedResultsZip {
  readonly outcome: SaveOutcome;
  readonly archive: ResultZip;
}

export async function saveExportResult(
  result: ExportResult,
  options: SaveExportResultOptions = {}
): Promise<SaveOutcome> {
  const fileName = safeExportFileName(options.fileName ?? result.fileName, {
    fallback: 'output',
  });
  return (options.saver ?? saveBlob)(result.blob, fileName, result.mimeType);
}

export async function saveCollectionResult(
  collection: ExportResultCollection,
  resultId: string,
  options: SaveCollectionResultOptions = {}
): Promise<SaveOutcome> {
  const result = collection.get(resultId);
  if (!result) {
    throw new RangeError(`Unknown result ID "${resultId}".`);
  }
  const outcome = await saveExportResult(result, options);
  if (outcome !== 'cancelled' && (options.releaseAfterSave ?? true)) {
    collection.remove(resultId);
  }
  return outcome;
}

export async function saveResultsZip(
  results: readonly ExportResult[],
  options: SaveResultsZipOptions = {}
): Promise<SavedResultsZip> {
  const entries = [...resultEntries(results)];
  if (options.report) {
    entries.push({
      fileName: exportReportFileName(options.report.plan.operation),
      blob: exportReportBlob(options.report),
    });
  }
  const { saver, fileName } = options;
  const archive = await createResultZip(entries, {
    ...(options.limits ? { limits: options.limits } : {}),
    ...(options.signal ? { signal: options.signal } : {}),
    ...(options.compressionLevel === undefined
      ? {}
      : { compressionLevel: options.compressionLevel }),
    fileName,
  });
  const outcome = await (saver ?? saveBlob)(
    archive.blob,
    archive.fileName,
    archive.blob.type
  );
  return Object.freeze({ outcome, archive });
}

export async function saveCollectionZip(
  collection: ExportResultCollection,
  options: SaveResultsZipOptions & {
    readonly releaseAfterSave?: boolean;
  } = {}
): Promise<SavedResultsZip> {
  const results = collection.list();
  const saved = await saveResultsZip(results, options);
  if (saved.outcome !== 'cancelled' && (options.releaseAfterSave ?? true)) {
    collection.clear();
  }
  return saved;
}

export async function saveExportReport(
  report: ExportReport,
  options: {
    readonly fileName?: string;
    readonly saver?: BlobSaver;
  } = {}
): Promise<SaveOutcome> {
  const blob = exportReportBlob(report);
  return (options.saver ?? saveBlob)(
    blob,
    safeExportFileName(
      options.fileName ?? exportReportFileName(report.plan.operation),
      { extension: 'json', fallback: 'export-report' }
    ),
    blob.type
  );
}

export function supportsSaveFilePicker(): boolean {
  return (
    typeof (
      globalThis as typeof globalThis & {
        showSaveFilePicker?: unknown;
      }
    ).showSaveFilePicker === 'function'
  );
}
