import { inject, Injectable } from '@angular/core';
import { PrintService as PrintApiService } from '@generated/swagger/print-api';
import { Platform } from '@angular/cdk/platform';
import { firstValueFrom, map } from 'rxjs';
import { Printer, PrinterType } from '../models';

import { injectDialog } from '@isa/ui/dialog';
import { PrintDialogComponent } from '../print-dialog/print-dialog.component';

/**
 * Service that provides access to printers available in the system.
 * Communicates with the print API to retrieve label and office printers.
 */
@Injectable({ providedIn: 'root' })
export class PrintService {
  #printService = inject(PrintApiService);
  #printDailog = injectDialog(PrintDialogComponent, { title: 'Drucken' });
  #platform = inject(Platform);

  /**
   * Retrieves a list of available label printers
   * @returns Observable of label printer array
   */
  labelPrinters(): Promise<Printer[]> {
    return firstValueFrom(
      this.#printService
        .PrintLabelPrinters()
        .pipe(map((res) => res.result as Printer[])),
    );
  }

  /**
   * Retrieves a list of available office printers
   * @returns Observable of office printer array
   */
  officePrinters(): Promise<Printer[]> {
    return firstValueFrom(
      this.#printService
        .PrintOfficePrinters()
        .pipe(map((res) => res.result as Printer[])),
    );
  }

  printers(printerType: PrinterType): Promise<Printer[]> {
    return printerType === PrinterType.LABEL
      ? this.labelPrinters()
      : this.officePrinters();
  }

  /**
   * Initiates a print operation with platform-specific optimizations.
   * On desktop, attempts to print directly using the default printer if available.
   * Falls back to showing a printer selection dialog if needed or on mobile devices.
   *
   * @param printerType - The type of printer to use (LABEL or OFFICE).
   * @param printFn - Function that performs the actual print operation with the selected printer.
   * @param directPrint - (Optional) If true, forces direct print without showing the dialog.
   *                      If false, always shows the print dialog.
   *                      If undefined, uses direct print only on non-mobile platforms with a selected printer.
   * @returns An object containing the selected printer if the operation was successful,
   *          or undefined if the operation was cancelled.
   */
  async print({
    printerType,
    printFn,
    directPrint,
  }: {
    printerType: PrinterType;
    printFn: (printer: Printer) => PromiseLike<unknown>;
    directPrint?: boolean | undefined;
  }): Promise<{ printer?: Printer }> {
    // Get the list of printers based on the printer type
    const printers = await this.printers(printerType);

    const selectedPrinter = printers.find((p) => p.selected);

    // If the platform is not Android or iOS, we can assume this is stationary devices
    // and we can try to print directly to the selected printer.
    // If it fails, we show the print dialog with the error.
    const directPrintAllowed =
      directPrint === undefined
        ? selectedPrinter && !(this.#platform.ANDROID || this.#platform.IOS)
        : directPrint;

    let error: unknown | undefined = undefined;
    if (directPrintAllowed && selectedPrinter) {
      try {
        await printFn(selectedPrinter);
        return { printer: selectedPrinter };
      } catch (e) {
        error = e;
      }
    }

    // Default behavior: show the print dialog
    // and let the user select a printer.
    const result = await firstValueFrom(
      this.#printDailog({
        data: {
          printers,
          error,
          print: printFn,
        },
      }).closed,
    );

    // Returns a printer if the print was successful
    // or undefined if the user canceled the dialog.
    return { printer: result?.printer };
  }
}
