import { makeAutoObservable, runInAction } from 'mobx';
import { newsApi, adminApi, type NewsItem } from '../../api/index.ts';
import type { ListNewsParams } from '../../api/news/index.ts';
import { NewsModel } from './news.model.ts';

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

  constructor(client = newsApi, adminClient = adminApi) {
    this.client = client;
    this.adminClient = adminClient;
    // @ts-expect-error (client is a known property)
    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((news) => news.slug === slug);
  }

  async fetch(params?: ListNewsParams) {
    this.isLoading = true;
    this.error = null;

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

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

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

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

    try {
      const payload = await this.adminClient.listNews(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 news';
        this.isLoading = false;
      });
      throw error;
    }
  }

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

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

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

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