import { inject, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { StepsModule } from 'primeng/steps';
import { DialogModule } from 'primeng/dialog';
import { ToastModule } from 'primeng/toast';
import { ProgressSpinnerModule } from 'primeng/progressspinner';
import { ButtonModule } from 'primeng/button';
import { ButtonGroupModule } from 'primeng/buttongroup';
import { ConfirmDialogModule } from 'primeng/confirmdialog';
import { TableModule } from 'primeng/table';
import { InputTextModule } from 'primeng/inputtext';
import { CheckboxModule } from 'primeng/checkbox';
import { DropdownModule } from 'primeng/dropdown';
import { TranslateModule } from '@ngx-translate/core';
import { ImageModule } from 'primeng/image';
import { TabMenuModule } from 'primeng/tabmenu';
import { MenubarModule } from 'primeng/menubar';
import { PanelMenuModule } from 'primeng/panelmenu';
import { CardModule } from 'primeng/card';
import { MultiSelectModule } from 'primeng/multiselect';
import { SplitButtonModule } from 'primeng/splitbutton';
import { CalendarModule } from 'primeng/calendar';
import { PaginatorModule } from 'primeng/paginator';
import { DataViewModule } from 'primeng/dataview';
import { TagModule } from 'primeng/tag';
import { SidebarModule } from 'primeng/sidebar';
import { FileUploadModule } from 'primeng/fileupload';
import { MenuModule } from 'primeng/menu';
import { ChipModule } from 'primeng/chip';
import { BadgeModule } from 'primeng/badge';
import { ContextMenuModule } from 'primeng/contextmenu';
import { DragDropModule } from 'primeng/dragdrop';
import { InputSwitchModule } from 'primeng/inputswitch';
import { SelectButtonModule } from 'primeng/selectbutton';
import { BreadcrumbModule } from 'primeng/breadcrumb';
import { OverlayPanelModule } from 'primeng/overlaypanel';
import { TerminalModule } from 'primeng/terminal';
import { RatingModule } from 'primeng/rating';
import { FocusTrapModule } from 'primeng/focustrap';
import { InputMaskModule } from 'primeng/inputmask';
import { TriStateCheckboxModule } from 'primeng/tristatecheckbox';
import { InputTextareaModule } from 'primeng/inputtextarea';
import { MenuBarComponent } from 'src/app/modules/shared/components/menu-bar/menu-bar.component';
import { SideMenuComponent } from './components/side-menu/side-menu.component';
import { TableComponent } from './components/table/table.component';
import { BoolPipe } from 'src/app/modules/shared/pipes/bool.pipe';
import { CustomDatePipe } from 'src/app/modules/shared/pipes/customDate.pipe';
import { FormPageComponent } from 'src/app/modules/shared/components/slidein/form-page.component';
import {
  FormPageContentDirective,
  FormPageHeaderDirective,
} from 'src/app/modules/shared/form';
import { ProtectPipe } from './pipes/protect.pipe';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { tokenInterceptor } from 'src/app/core/token.interceptor';
import {
  CustomActionsDirective,
  CustomRowActionsDirective,
} from 'src/app/modules/shared/components/table/table';
import { ColumnSelectorComponent } from 'src/app/modules/shared/components/table/column-selector/column-selector.component';
import { provideApollo } from 'apollo-angular';
import { Logger } from 'src/app/service/logger.service';
import { ConfigService } from 'src/app/service/config.service';
import { KeycloakService } from 'keycloak-angular';
import { HttpLink } from 'apollo-angular/http';
import { Router } from '@angular/router';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { createClient } from 'graphql-ws';
import { InMemoryCache, split } from '@apollo/client/core';
import { getMainDefinition } from '@apollo/client/utilities';
import { Kind, OperationTypeNode } from 'graphql/index';
import { HistorySidebarComponent } from 'src/app/modules/shared/components/history/history-sidebar.component';
import { SliderModule } from 'primeng/slider';
import { ChipInputComponent } from './components/chip-input/chip-input.component';

const sharedModules = [
  StepsModule,
  DialogModule,
  ToastModule,
  ProgressSpinnerModule,
  ButtonModule,
  ButtonGroupModule,
  FormsModule,
  ReactiveFormsModule,
  ConfirmDialogModule,
  TableModule,
  InputTextModule,
  CheckboxModule,
  DropdownModule,
  TranslateModule,
  ImageModule,
  TabMenuModule,
  MenubarModule,
  PanelMenuModule,
  CardModule,
  MultiSelectModule,
  SplitButtonModule,
  CalendarModule,
  PaginatorModule,
  DataViewModule,
  TagModule,
  SidebarModule,
  FileUploadModule,
  MenuModule,
  ChipModule,
  BadgeModule,
  ContextMenuModule,
  DragDropModule,
  InputSwitchModule,
  SelectButtonModule,
  BreadcrumbModule,
  OverlayPanelModule,
  SplitButtonModule,
  TerminalModule,
  RatingModule,
  FocusTrapModule,
  InputMaskModule,
  TriStateCheckboxModule,
  InputTextareaModule,
  TranslateModule,
  SliderModule,
];

const sharedComponents = [
  MenuBarComponent,
  SideMenuComponent,
  FormPageComponent,
  FormPageHeaderDirective,
  FormPageContentDirective,
  TableComponent,
  ColumnSelectorComponent,
  CustomActionsDirective,
  CustomRowActionsDirective,
  ProtectPipe,
  BoolPipe,
  CustomDatePipe,
  HistorySidebarComponent,
];

function debounce(func: (...args: unknown[]) => void, wait: number) {
  let timeout: ReturnType<typeof setTimeout>;
  return (...args: unknown[]) => {
    clearTimeout(timeout);
    timeout = setTimeout(() => func(...args), wait);
  };
}

@NgModule({
  declarations: [...sharedComponents, ChipInputComponent],
  imports: [CommonModule, ...sharedModules],
  exports: [...sharedModules, ...sharedComponents, ChipInputComponent],
  providers: [
    provideHttpClient(withInterceptors([tokenInterceptor])),
    provideApollo(() => {
      const logger = new Logger('graphql');

      const settings = inject(ConfigService);
      const keycloak = inject(KeycloakService);
      const httpLink = inject(HttpLink);
      const router = inject(Router);

      let isConnected = false;
      let retryCount = 0;
      const maxRetries = 5;

      const http = httpLink.create({
        uri: `${settings.settings.api.url}/graphql`,
      });

      const ws = new GraphQLWsLink(
        createClient({
          url: `${settings.settings.api.wsUrl}/graphql`,
          retryAttempts: Infinity,
          shouldRetry: () => true,
          keepAlive: 10000,
          connectionParams: async () => {
            return {
              Authorization: `Bearer ${await keycloak.getToken()}`,
            };
          },
          on: {
            connected: () => {
              logger.info('WebSocket connected');
              isConnected = true;
              retryCount = 0;
            },
            closed: debounce(async () => {
              if (!isConnected) {
                retryCount++;
                if (retryCount >= maxRetries) {
                  logger.error('WebSocket disconnected');
                  await router.navigate(['/error/unavailable']);
                }
              }
              isConnected = false;
            }, 1000),
          },
        })
      );

      const link = split(
        ({ query }) => {
          const definition = getMainDefinition(query);
          return (
            definition.kind === Kind.OPERATION_DEFINITION &&
            definition.operation === OperationTypeNode.SUBSCRIPTION
          );
        },
        ws,
        http
      );

      return {
        link: link,
        cache: new InMemoryCache(),

        defaultOptions: {
          watchQuery: {
            fetchPolicy: 'no-cache',
          },
          query: {
            fetchPolicy: 'no-cache',
          },
          mutate: {
            fetchPolicy: 'no-cache',
          },
        },
      };
    }),
  ],
})
export class SharedModule {}
