import {
  patchState,
  signalStore,
  withComputed,
  withMethods,
  withProps,
  withState,
} from '@ngrx/signals';
import {
  addEntity,
  removeEntity,
  updateEntity,
  withEntities,
} from '@ngrx/signals/entities';
import { z } from 'zod';
import {
  AddTabSchema,
  PatchTabSchema,
  TabLocationSchema,
  Tab,
  TabLocation,
  TabLocationHistory,
} from './schemas';
import { TAB_CONFIG } from './tab-config';
import { TabHistoryPruner } from './tab-history-pruning';
import { computed, inject } from '@angular/core';
import { withDevtools } from '@angular-architects/ngrx-toolkit';
import { CORE_TAB_ID_GENERATOR } from './tab-id.generator';
import { withStorage, UserStorageProvider } from '@isa/core/storage';
import { logger } from '@isa/core/logging';

export const TabService = signalStore(
  { providedIn: 'root' },
  withDevtools('TabService'),
  withStorage('tabs', UserStorageProvider, { autosave: true }),
  withState<{ activatedTabId: number | null }>({
    activatedTabId: null,
  }),
  withEntities<Tab>(),
  withProps(
    (
      _,
      idGenerator = inject(CORE_TAB_ID_GENERATOR),
      config = inject(TAB_CONFIG),
    ) => ({
      _generateId: idGenerator,
      _config: config,
      _logger: logger({ service: 'TabService' }),
    }),
  ),
  withComputed((store) => ({
    activatedTab: computed<Tab | null>(() => {
      const activeTabId = store.activatedTabId();
      if (activeTabId === null) {
        return null;
      }
      return store.entities().find((e) => e.id === activeTabId) ?? null;
    }),
  })),
  withMethods((store) => ({
    addTab(add: z.input<typeof AddTabSchema>) {
      const parsed = AddTabSchema.parse(add);
      const tab: Tab = {
        id: parsed.id ?? store._generateId(),
        name: parsed.name,
        subtitle: parsed.subtitle,
        createdAt: Date.now(),
        activatedAt: parsed.activatedAt,
        tags: parsed.tags,
        metadata: parsed.metadata,
        location: { current: -1, locations: [] },
      };
      patchState(store, addEntity(tab));
      store._logger.info('Tab added', () => ({
        tabId: tab.id,
        name: tab.name,
        tags: tab.tags,
      }));
      return tab;
    },
    activateTab(id: number) {
      const tab = store.entityMap()[id];
      patchState(store, { activatedTabId: id });

      if (!tab) {
        store._logger.warn('Cannot activate non-existent tab', () => ({
          tabId: id,
        }));
        return;
      }

      const changes: Partial<Tab> = { activatedAt: Date.now() };
      patchState(store, updateEntity({ id, changes }));

      store._logger.debug('Tab activated', () => ({ tabId: id }));
    },
    patchTab(id: number, changes: z.infer<typeof PatchTabSchema>) {
      const currentTab = store.entityMap()[id];

      if (!currentTab) {
        store._logger.warn('Cannot patch non-existent tab', () => ({
          tabId: id,
        }));
        return;
      }

      const patchedMetadata = changes.metadata
        ? { ...currentTab.metadata, ...changes.metadata }
        : currentTab.metadata;

      const entityChanges: Partial<Tab> = {
        ...changes,
        metadata: patchedMetadata,
      };

      patchState(store, updateEntity({ id, changes: entityChanges }));
      store._logger.debug('Tab patched', () => ({
        tabId: id,
        changedFields: Object.keys(changes),
      }));
    },
    patchTabMetadata(id: number, metadata: Record<string, unknown>) {
      const currentTab = store.entityMap()[id];

      if (!currentTab) {
        store._logger.warn(
          'Cannot patch metadata for non-existent tab',
          () => ({ tabId: id }),
        );
        return;
      }

      const changes: Partial<Tab> = {
        metadata: { ...currentTab.metadata, ...metadata },
      };
      patchState(store, updateEntity({ id, changes }));
      store._logger.debug('Tab metadata patched', () => ({
        tabId: id,
        metadataKeys: Object.keys(metadata),
      }));
    },
    removeTab(id: number) {
      const wasActive = store.activatedTabId() === id;
      patchState(store, removeEntity(id));
      if (wasActive) {
        patchState(store, { activatedTabId: null });
      }
      store._logger.info('Tab removed', () => ({ tabId: id, wasActive }));
    },
    navigateToLocation(
      id: number,
      location: z.input<typeof TabLocationSchema>,
    ) {
      const parsed: TabLocation = TabLocationSchema.parse(location);
      const currentTab = store.entityMap()[id];
      if (!currentTab) {
        store._logger.warn(
          'Cannot navigate to location for non-existent tab',
          () => ({ tabId: id }),
        );
        return null;
      }

      const currentLocation = currentTab.location;

      // First, limit forward history if configured
      const maxForwardHistory =
        typeof currentTab.metadata['maxForwardHistory'] === 'number'
          ? currentTab.metadata['maxForwardHistory']
          : store._config.maxForwardHistory;

      const { locations: limitedLocations } =
        TabHistoryPruner.pruneForwardHistory(
          currentLocation.locations,
          currentLocation.current,
          maxForwardHistory,
        );

      // Add new location
      const newLocations: TabLocation[] = [
        ...limitedLocations.slice(0, currentLocation.current + 1),
        parsed,
      ];

      let newLocationHistory: TabLocationHistory = {
        current: newLocations.length - 1,
        locations: newLocations,
      };

      // Apply size-based pruning if needed
      const pruningResult = TabHistoryPruner.pruneHistory(
        newLocationHistory,
        store._config,
        currentTab.metadata,
      );

      if (pruningResult.entriesRemoved > 0) {
        if (store._config.logPruning) {
          store._logger.info('Tab history pruned', () => ({
            tabId: id,
            entriesRemoved: pruningResult.entriesRemoved,
            strategy: pruningResult.strategy,
          }));
        }

        newLocationHistory = {
          current: pruningResult.newCurrent,
          locations: pruningResult.locations,
        };
      }

      // Validate index integrity
      const { index: validatedCurrent, wasInvalid } =
        TabHistoryPruner.validateLocationIndex(
          newLocationHistory.locations,
          newLocationHistory.current,
        );

      if (wasInvalid && store._config.enableIndexValidation) {
        store._logger.warn('Invalid location index corrected', () => ({
          tabId: id,
          invalidIndex: newLocationHistory.current,
          correctedIndex: validatedCurrent,
        }));
        newLocationHistory.current = validatedCurrent;
      }

      const changes: Partial<Tab> = {
        location: newLocationHistory,
      };
      patchState(store, updateEntity({ id, changes }));

      store._logger.debug('Navigated to location', () => ({
        tabId: id,
        url: parsed.url,
        historyLength: newLocationHistory.locations.length,
        currentIndex: newLocationHistory.current,
      }));

      return parsed;
    },
    navigateBack(id: number) {
      const currentTab = store.entityMap()[id];
      if (!currentTab) {
        store._logger.warn('Cannot navigate back for non-existent tab', () => ({
          tabId: id,
        }));
        return null;
      }

      const currentLocation = currentTab.location;

      // Validate current index before navigation
      const { index: validatedCurrent } =
        TabHistoryPruner.validateLocationIndex(
          currentLocation.locations,
          currentLocation.current,
        );

      if (validatedCurrent <= 0) {
        store._logger.debug(
          'Cannot navigate back - at beginning of history',
          () => ({ tabId: id }),
        );
        return null;
      }

      const newCurrent = validatedCurrent - 1;
      const previousLocation = currentLocation.locations[newCurrent];

      const changes: Partial<Tab> = {
        location: {
          ...currentLocation,
          current: newCurrent,
        },
      };
      patchState(store, updateEntity({ id, changes }));

      store._logger.debug('Navigated back', () => ({
        tabId: id,
        newIndex: newCurrent,
        url: previousLocation.url,
      }));

      return previousLocation;
    },
    navigateForward(id: number) {
      const currentTab = store.entityMap()[id];
      if (!currentTab) {
        store._logger.warn(
          'Cannot navigate forward for non-existent tab',
          () => ({ tabId: id }),
        );
        return null;
      }

      const currentLocation = currentTab.location;

      // Validate current index before navigation
      const { index: validatedCurrent } =
        TabHistoryPruner.validateLocationIndex(
          currentLocation.locations,
          currentLocation.current,
        );

      if (validatedCurrent >= currentLocation.locations.length - 1) {
        store._logger.debug(
          'Cannot navigate forward - at end of history',
          () => ({ tabId: id }),
        );
        return null;
      }

      const newCurrent = validatedCurrent + 1;
      const nextLocation = currentLocation.locations[newCurrent];

      const changes: Partial<Tab> = {
        location: {
          ...currentLocation,
          current: newCurrent,
        },
      };
      patchState(store, updateEntity({ id, changes }));

      store._logger.debug('Navigated forward', () => ({
        tabId: id,
        newIndex: newCurrent,
        url: nextLocation.url,
      }));

      return nextLocation;
    },
    clearLocationHistory(id: number) {
      const currentTab = store.entityMap()[id];
      if (!currentTab) {
        store._logger.warn(
          'Cannot clear location history for non-existent tab',
          () => ({ tabId: id }),
        );
        return;
      }

      const historyLength = currentTab.location.locations.length;

      const changes: Partial<Tab> = {
        location: {
          current: -1,
          locations: [],
        },
      };
      patchState(store, updateEntity({ id, changes }));

      store._logger.debug('Location history cleared', () => ({
        tabId: id,
        clearedEntries: historyLength,
      }));
    },
    /**
     * Gets the current location for a tab.
     *
     * IMPORTANT: This method has a side effect - if index validation is enabled
     * and an invalid index is detected, it will automatically correct the index
     * in the store, triggering state updates and storage autosave.
     *
     * @param id - The tab ID
     * @returns The current location or null if tab doesn't exist or history is empty
     */
    getCurrentLocation(id: number) {
      const currentTab = store.entityMap()[id];
      if (!currentTab) return null;

      const currentLocation = currentTab.location;

      // Validate current index
      const { index: validatedCurrent, wasInvalid } =
        TabHistoryPruner.validateLocationIndex(
          currentLocation.locations,
          currentLocation.current,
        );

      if (wasInvalid && store._config.enableIndexValidation) {
        store._logger.warn(
          'Invalid location index corrected in getCurrentLocation',
          () => ({
            tabId: id,
            invalidIndex: currentLocation.current,
            correctedIndex: validatedCurrent,
          }),
        );

        // Correct the invalid index in store (SIDE EFFECT)
        const changes: Partial<Tab> = {
          location: {
            ...currentLocation,
            current: validatedCurrent,
          },
        };
        patchState(store, updateEntity({ id, changes }));
      }

      if (
        validatedCurrent < 0 ||
        validatedCurrent >= currentLocation.locations.length
      ) {
        return null;
      }

      return currentLocation.locations[validatedCurrent];
    },
    updateCurrentLocation(id: number, updates: Partial<TabLocation>) {
      const currentTab = store.entityMap()[id];
      if (!currentTab) {
        store._logger.warn(
          'Cannot update current location for non-existent tab',
          () => ({ tabId: id }),
        );
        return null;
      }

      const currentLocation = currentTab.location;
      if (
        currentLocation.current < 0 ||
        currentLocation.current >= currentLocation.locations.length
      ) {
        store._logger.warn(
          'Cannot update current location - invalid index',
          () => ({
            tabId: id,
            currentIndex: currentLocation.current,
            historyLength: currentLocation.locations.length,
          }),
        );
        return null;
      }

      const updatedLocation = {
        ...currentLocation.locations[currentLocation.current],
        ...updates,
      };

      const newLocations = [...currentLocation.locations];
      newLocations[currentLocation.current] = updatedLocation;

      const changes: Partial<Tab> = {
        location: {
          ...currentLocation,
          locations: newLocations,
        },
      };
      patchState(store, updateEntity({ id, changes }));

      store._logger.debug('Current location updated', () => ({
        tabId: id,
        updatedFields: Object.keys(updates),
        url: updatedLocation.url,
      }));

      return updatedLocation;
    },
  })),
);
