'use client';

import { runInAction } from 'mobx';

import type { components } from '@/lib/gen';

import { RootStore, Substore } from './rootStore';
import { makeAutoObservableSubstore } from './utils';

// All V2 stuff
export type UserNotificationV2Schema =
  components['schemas']['UserNotificationV2Schema'];

export type NotificationV2Schema = NonNullable<
  components['schemas']['UserNotificationV2Schema']['notifications']
>[number];

export type FilteredNotificationV2Schema<
  T extends NotificationV2Schema['notification_type'],
> = NotificationV2Schema & { notification_type: T };

export type SupportedNotificationV2Schema = FilteredNotificationV2Schema<
  | 'announcement'
  | 'clip_like'
  | 'scene_like'
  | 'playlist_like'
  | 'follow'
  | 'persona_follow'
  | 'persona_favorite'
  | 'persona_used'
  | 'clip_comment'
  | 'scene_comment'
  | 'comment_like'
  | 'comment_reply'
  | 'clip_remix'
  | 'comment_mention'
  | 'caption_mention'
  | 'share_clip'
  | 'hook_like'
  | 'hook_comment'
  | 'hook_comment_reply'
  | 'hook_comment_mention'
  | 'hook_comment_like'
  | 'clip_create_followee'
  | 'video_cover_hook_like'
  | 'video_cover_hook_comment'
  | 'video_cover_hook_comment_reply'
  | 'video_cover_hook_comment_mention'
  | 'video_cover_hook_comment_like'
>;

export const SUPPORTED_NOTIFICATION_V2_TYPES = new Set<
  NotificationV2Schema['notification_type']
>([
  'announcement',
  'clip_like',
  'scene_like',
  'playlist_like',
  'follow',
  'persona_follow',
  'persona_favorite',
  'persona_used',
  'clip_comment',
  'scene_comment',
  'comment_like',
  'comment_reply',
  'clip_create_followee',
  'clip_remix',
  'comment_mention',
  'caption_mention',
  'share_clip',
  'hook_like',
  'hook_comment',
  'hook_comment_reply',
  'hook_comment_mention',
  'hook_comment_like',
  'video_cover_hook_like',
  'video_cover_hook_comment',
  'video_cover_hook_comment_reply',
  'video_cover_hook_comment_mention',
  'video_cover_hook_comment_like',
]);

export const HIDE_NOTIFICATION_V2_TYPES = new Set<
  NotificationV2Schema['notification_type']
>(['delete']);

export function sortNotificationsV2(
  a: NotificationV2Schema,
  b: NotificationV2Schema
) {
  const dateA = new Date(a.updated_at);
  const dateB = new Date(b.updated_at);
  if (dateA > dateB) {
    return -1;
  } else if (dateA < dateB) {
    return 1;
  }
  return 0;
}
// V2 stuff ends here
export class NotificationsStore implements Substore {
  notifiedAt?: components['schemas']['UserNotificationV2Schema']['notified_at'];
  notificationsV2: NotificationV2Schema[] = [];
  unreadCount = 0;
  isLoading = false;
  hasMore = true;

  readonly root: RootStore;
  get apiClient() {
    return this.root.apiClient;
  }
  get logger() {
    return this.root.logger;
  }
  // DO NOT ADD INIT LOGIC IN constructor. this will run on every page! considering adding any it in the component init logic instead
  constructor(root: RootStore) {
    this.root = root;
    makeAutoObservableSubstore(this);
  }

  calculateUnreadCount = () => {
    return this.notificationsV2.reduce(
      (acc, notification) => (notification.is_read ? acc : acc + 1),
      0
    );
  };

  /**
   * The timestamp of the most recent notification
   */
  get lastDateTimeUTC() {
    return this.notificationsV2[0]?.updated_at;
  }

  get allNotifications() {
    return this.notificationsV2;
  }

  async loadNotifications(afterDateTimeUTC = this.notifiedAt) {
    if (this.isLoading || !this.root.session.userId) {
      return;
    }

    runInAction(() => {
      this.isLoading = true;
    });
    const response = await this.apiClient.GET('/api/notification/v2', {
      params: { query: { after_datetime_utc: afterDateTimeUTC } },
    });

    runInAction(() => {
      this.isLoading = false;

      if (!response.data) {
        return;
      }

      if (response.data.notifications) {
        this.notifiedAt = response.data.notified_at;

        // Create an ID-based mapping of new notifications
        const newNotifications = Object.fromEntries(
          response.data.notifications
            .filter(
              (notification) =>
                !HIDE_NOTIFICATION_V2_TYPES.has(notification.notification_type)
            )
            .map((notification) => [notification.id, notification] as const)
        );

        // Update any existing notifications instead of inserting duplicates
        for (const i in this.notificationsV2) {
          const id = this.notificationsV2[i].id;
          if (id in newNotifications) {
            this.notificationsV2[i] = newNotifications[id];
            delete newNotifications[id];
          }
        }

        // Add all the truly new notifications
        this.notificationsV2.unshift(...Object.values(newNotifications));

        // Make sure everything is in the correct order
        this.notificationsV2.sort(sortNotificationsV2);

        // Update unread count
        this.unreadCount = this.calculateUnreadCount();
      }
    });
  }

  async loadMoreNotifications() {
    if (this.isLoading || !this.root.session.userId || !this.hasMore) {
      return;
    }

    const lastNotification =
      this.notificationsV2[this.notificationsV2.length - 1];
    if (!lastNotification) return;

    runInAction(() => {
      this.isLoading = true;
    });

    const response = await this.apiClient.GET('/api/notification/v2', {
      params: {
        query: {
          before_datetime_utc: lastNotification.updated_at,
        },
      },
    });

    runInAction(() => {
      this.isLoading = false;

      if (!response.data) {
        return;
      }

      if (response.data.notifications) {
        // Add new notifications
        const newNotifications = response.data.notifications.filter(
          (notification) =>
            !HIDE_NOTIFICATION_V2_TYPES.has(notification.notification_type) &&
            !this.notificationsV2.some((n) => n.id === notification.id)
        );

        // If we got no new notifications, there are no more to load
        if (newNotifications.length === 0) {
          this.hasMore = false;
          return;
        }

        this.notificationsV2.push(...newNotifications);
        this.notificationsV2.sort(sortNotificationsV2);
      } else {
        // If we got no notifications array, there are no more to load
        this.hasMore = false;
      }
    });
  }

  async markAsRead(...notificationIds: string[]) {
    await this.apiClient.POST('/api/notification/v2/read', {
      body: {
        ids: notificationIds,
      },
    });

    runInAction(() => {
      // Update `is_read` state
      for (const notification of this.notificationsV2) {
        if (notificationIds.includes(notification.id)) {
          notification.is_read = true;
        }
      }

      // Update unread count
      this.unreadCount = this.calculateUnreadCount();
    });
  }

  async markAllAsRead(
    beforeDateTimeUTC = this.notifiedAt || new Date().toISOString()
  ) {
    await this.apiClient.POST('/api/notification/v2/read', {
      body: {
        all: true,
        before_datetime_utc: beforeDateTimeUTC,
      },
    });

    runInAction(() => {
      // Update `is_read` state
      const beforeDateTime = new Date(beforeDateTimeUTC);
      for (const notification of this.notificationsV2) {
        if (new Date(notification.updated_at) < beforeDateTime) {
          notification.is_read = true;
        }
      }

      // Update unread count
      this.unreadCount = this.calculateUnreadCount();
    });
  }

  async dismiss(...notificationIds: string[]) {
    await this.apiClient.POST('/api/notification/suppress', {
      body: {
        ids: notificationIds,
      },
    });

    runInAction(() => {
      // Filter out dismissed notifications
      this.notificationsV2 = this.notificationsV2.filter((notification) =>
        notificationIds.includes(notification.id)
      );

      // Update unread count
      this.unreadCount = this.calculateUnreadCount();
    });
  }

  async setFollowing(handle: string, follow = true) {
    const { response } = await this.apiClient.POST('/api/profiles/follow', {
      body: {
        unfollow: !follow,
        handle,
      },
    });

    if (!response.ok) {
      return !follow;
    }

    runInAction(() => {
      // Update any relevant `is_following` state
      for (const notification of this.notificationsV2) {
        if ('user_profiles' in notification && notification.user_profiles) {
          for (const profile of notification.user_profiles) {
            if (profile.handle === handle) {
              profile.is_following = follow;
            }
          }
        }
      }
    });

    return follow;
  }
}
