import { InjectionToken, Injectable, Type, signal } from '@angular/core';
import { Observable, Subject, map } from 'rxjs';
import { ConfirmDeleteModalComponent } from '../../shared/components/confirm-delete-modal/confirm-delete-modal.component';
import { ConfirmModalComponent } from '../../shared/components/confirm-modal/confirm-modal.component';
import { QrModalComponent } from '../../shared/components/qr-modal/qr-modal.component';
import { SimpleModalComponent } from '../../shared/components/simple-modal/simple-modal.component';

export type ModalSize = 'qr' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
export type ModalDismissReason = 'backdrop' | 'escape' | 'programmatic' | 'replaced';

export interface ModalConfig<TData = unknown> {
  title?: string;
  data?: TData;
  size?: ModalSize;
  closeOnBackdrop?: boolean;
  closeOnEscape?: boolean;
  showCloseButton?: boolean;
}

export interface NormalizedModalConfig<TData = unknown> extends Omit<
  ModalConfig<TData>,
  'size' | 'closeOnBackdrop' | 'closeOnEscape' | 'showCloseButton'
> {
  size: ModalSize;
  closeOnBackdrop: boolean;
  closeOnEscape: boolean;
  showCloseButton: boolean;
}

export interface ConfirmModalData {
  content: string;
  confirmLabel: string;
  cancelLabel: string;
}

export interface ConfirmModalConfig extends Omit<ModalConfig<ConfirmModalData>, 'data'> {
  content: string;
  confirmLabel?: string;
  cancelLabel?: string;
}

export interface SimpleModalData {
  content: string;
  buttonLabel: string;
}

export interface SimpleModalConfig extends Omit<ModalConfig<SimpleModalData>, 'data'> {
  content: string;
  buttonLabel?: string;
}

export interface QrModalData {
  id: string | number;
  date: string;
  ticket: string;
}

export interface QrModalConfig extends Omit<ModalConfig<QrModalData>, 'data' | 'title'> {
  title: string;
  id: string | number;
  date: string;
  ticket: string;
}

export interface ActiveModalState<TResult = unknown, TData = unknown> {
  component: Type<unknown>;
  config: NormalizedModalConfig<TData>;
  ref: ModalRef<unknown>;
}

export const MODAL_DATA = new InjectionToken<unknown>('MODAL_DATA');

const DEFAULT_MODAL_CONFIG: Pick<
  NormalizedModalConfig,
  'size' | 'closeOnBackdrop' | 'closeOnEscape' | 'showCloseButton'
> = {
  size: 'md',
  closeOnBackdrop: true,
  closeOnEscape: true,
  showCloseButton: true,
};

const DEFAULT_CONFIRM_MODAL_LABELS = {
  confirmLabel: 'Confirmar',
  cancelLabel: 'Cancelar',
} satisfies Pick<ConfirmModalData, 'confirmLabel' | 'cancelLabel'>;

export class ModalRef<TResult = unknown> {
  private readonly afterClosedSubject = new Subject<TResult | undefined>();
  private closed = false;

  readonly afterClosed$: Observable<TResult | undefined> = this.afterClosedSubject.asObservable();
  readonly dismissReason = signal<ModalDismissReason | null>(null);

  constructor(
    private readonly closeHandler: (result?: TResult) => void,
    private readonly dismissHandler: (reason: ModalDismissReason) => void,
  ) {}

  close(result?: TResult): void {
    if (this.closed) {
      return;
    }

    this.closeHandler(result);
  }

  dismiss(reason: ModalDismissReason = 'programmatic'): void {
    if (this.closed) {
      return;
    }

    this.dismissHandler(reason);
  }

  finalize(result?: TResult, dismissReason?: ModalDismissReason): void {
    if (this.closed) {
      return;
    }

    this.closed = true;
    this.dismissReason.set(dismissReason ?? null);
    this.afterClosedSubject.next(result);
    this.afterClosedSubject.complete();
  }
}

@Injectable({
  providedIn: 'root',
})
export class ModalService {
  private readonly activeModalState = signal<ActiveModalState | null>(null);
  readonly activeModal = this.activeModalState.asReadonly();

  open<TComponent, TResult = unknown, TData = unknown>(
    component: Type<TComponent>,
    config: ModalConfig<TData> = {},
  ): ModalRef<TResult> {
    this.activeModalState()?.ref.dismiss('replaced');

    let ref!: ModalRef<TResult>;
    ref = new ModalRef<TResult>(
      (result) => this.close(ref, result),
      (reason) => this.dismiss(ref, reason),
    );

    this.activeModalState.set({
      component,
      config: this.normalizeConfig(config),
      ref: ref as ModalRef<unknown>,
    });

    return ref;
  }

  openConfirm(config: ConfirmModalConfig): Observable<boolean> {
    return this.openConfirmRef(config).afterClosed$.pipe(map((result) => result === true));
  }

  openConfirmDelete(config: ConfirmModalConfig): Observable<boolean> {
    return this.openConfirmDeleteRef(config).afterClosed$.pipe(map((result) => result === true));
  }

  openConfirmRef(config: ConfirmModalConfig): ModalRef<boolean> {
    return this.open(ConfirmModalComponent, this.buildConfirmModalConfig(config));
  }

  openConfirmDeleteRef(config: ConfirmModalConfig): ModalRef<boolean> {
    return this.open(
      ConfirmDeleteModalComponent,
      this.buildConfirmModalConfig({ size: 'sm', ...config }),
    );
  }

  openSimple(config: SimpleModalConfig): Observable<void> {
    return this.openSimpleRef(config).afterClosed$.pipe(map(() => undefined));
  }

  openSimpleRef(config: SimpleModalConfig): ModalRef<void> {
    return this.open(SimpleModalComponent, this.buildSimpleModalConfig(config));
  }

  openQr(config: QrModalConfig): Observable<void> {
    return this.openQrRef(config).afterClosed$.pipe(map(() => undefined));
  }

  openQrRef(config: QrModalConfig): ModalRef<void> {
    const { title, id, date, ticket, size = 'qr', ...modalConfig } = config;

    return this.open(QrModalComponent, {
      ...modalConfig,
      title,
      size,
      data: { id, date, ticket },
    });
  }

  private close<TResult>(ref: ModalRef<TResult>, result?: TResult): void {
    if (this.activeModalState()?.ref !== ref) {
      return;
    }

    ref.finalize(result);
    this.activeModalState.set(null);
  }

  private dismiss<TResult>(
    ref: ModalRef<TResult>,
    reason: ModalDismissReason = 'programmatic',
  ): void {
    if (this.activeModalState()?.ref !== ref) {
      return;
    }

    ref.finalize(undefined, reason);
    this.activeModalState.set(null);
  }

  private normalizeConfig<TData>(config: ModalConfig<TData>): NormalizedModalConfig<TData> {
    return {
      ...DEFAULT_MODAL_CONFIG,
      ...config,
    };
  }

  private buildConfirmModalConfig(config: ConfirmModalConfig): ModalConfig<ConfirmModalData> {
    const {
      content,
      confirmLabel = DEFAULT_CONFIRM_MODAL_LABELS.confirmLabel,
      cancelLabel = DEFAULT_CONFIRM_MODAL_LABELS.cancelLabel,
      ...modalConfig
    } = config;

    return {
      ...modalConfig,
      data: {
        content,
        confirmLabel,
        cancelLabel,
      },
    };
  }

  private buildSimpleModalConfig(config: SimpleModalConfig): ModalConfig<SimpleModalData> {
    const { content, buttonLabel = 'Entendido', ...modalConfig } = config;

    return {
      ...modalConfig,
      data: {
        content,
        buttonLabel,
      },
    };
  }
}
