import { makeAutoObservable, runInAction } from 'mobx';

import { servicesApi, adminApi, type ServiceItem } from '../../api';
import type { ListServicesParams } from '../../api/services';
import { ServiceModel } from './service.model.ts';

export class ServicesCollectionModel {
  readonly items = new Map<string, ServiceModel>();
  private readonly client = servicesApi;
  private readonly adminClient = adminApi;
  isLoading = false;
  error: string | null = null;
  total = 0;
  limit = 0;
  offset = 0;

  constructor(client = servicesApi, adminClient = adminApi) {
    this.client = client;
    this.adminClient = adminClient;
    // @ts-expect-error (Client is expected here)
    makeAutoObservable(this, { client: false, adminClient: false }, { autoBind: true });
  }

  get list() {
    return Array.from(this.items.values());
  }

  get isEmpty() {
    return this.items.size === 0;
  }

  getById(id: string | number) {
    return this.items.get(String(id));
  }

  getBySlug(slug: string) {
    return this.list.find((service) => service.slug === slug);
  }

  async fetch(params?: ListServicesParams, options?: { replace?: boolean }) {
    this.isLoading = true;
    this.error = null;
    const shouldReplace = options?.replace ?? false;

    try {
      const payload = await this.client.list(params);
      runInAction(() => {
        if (shouldReplace) {
          this.items.clear();
        }
        this.total = payload.total;
        this.limit = payload.limit;
        this.offset = payload.offset;
        this.hydrate(payload.items);
        this.isLoading = false;
      });
    } catch (error) {
      runInAction(() => {
        this.error = error instanceof Error ? error.message : 'Failed to load services';
        this.isLoading = false;
      });
      throw error;
    }
  }

  async fetchBySlug(slugOrId: string) {
    this.isLoading = true;
    this.error = null;

    try {
      const payload = await this.client.get(slugOrId);
      runInAction(() => {
        this.upsert(payload);
        this.isLoading = false;
      });
      return this.getBySlug(payload.slug) ?? this.getById(payload.id);
    } catch (error) {
      runInAction(() => {
        this.error = error instanceof Error ? error.message : 'Failed to load services';
        this.isLoading = false;
      });
      throw error;
    }
  }

  async fetchAdmin(params?: ListServicesParams, options?: { replace?: boolean }) {
    this.isLoading = true;
    this.error = null;
    const shouldReplace = options?.replace ?? false;

    try {
      const payload = await this.adminClient.listAdminServices(params);
      runInAction(() => {
        if (shouldReplace) {
          this.items.clear();
        }
        this.total = payload.total;
        this.limit = payload.limit;
        this.offset = payload.offset;
        this.hydrate(payload.items);
        this.isLoading = false;
      });
    } catch (error) {
      runInAction(() => {
        this.error = error instanceof Error ? error.message : 'Failed to load services';
        this.isLoading = false;
      });
      throw error;
    }
  }

  async fetchBySlugAdmin(slugOrId: string) {
    this.isLoading = true;
    this.error = null;

    try {
      const payload = await this.adminClient.getAdminService(slugOrId);
      runInAction(() => {
        this.upsert(payload);
        this.isLoading = false;
      });
      return this.getBySlug(payload.slug) ?? this.getById(payload.id);
    } catch (error) {
      runInAction(() => {
        this.error = error instanceof Error ? error.message : 'Failed to load services';
        this.isLoading = false;
      });
      throw error;
    }
  }

  private hydrate(payload: ServiceItem[]) {
    payload.forEach((item) => {
      this.upsert(item);
    });
  }

  private upsert(item: ServiceItem) {
    const key = String(item.id);
    const existing = this.items.get(key);

    if (existing) {
      existing.update(item);
      return;
    }

    this.items.set(key, new ServiceModel(item));
  }
}
