/**
 * Copyright (c) 2023-present Plane Software, Inc. and contributors
 * SPDX-License-Identifier: AGPL-3.0-only
 * See the LICENSE file for details.
 */

import { action, computed, makeObservable, observable, runInAction } from "mobx";
import type {
  TExternalContourBoardDirection,
  TExternalContourBoardFilter,
  TExternalContourBoardSorting,
  TExternalContourRequest,
  TInboxIssueCurrentTab,
} from "@plane/types";
import { EInboxIssueCurrentTab } from "@plane/types";
import { ExternalContourService } from "@/services/external-contours";
import type { CoreRootStore } from "../root.store";

type TLoader = "init-loading" | "loading" | undefined;

const DEFAULT_SORTING: TExternalContourBoardSorting = { order_by: "updated_at", sort_by: "desc" };

const sanitizeBoardFilters = (filters: Partial<TExternalContourBoardFilter>): Partial<TExternalContourBoardFilter> =>
  Object.fromEntries(
    Object.entries(filters).flatMap(([key, value]) => {
      if (value === undefined || value === null || value === "") return [];
      if (Array.isArray(value) && value.length === 0) return [];
      return [[key, value]];
    })
  ) as Partial<TExternalContourBoardFilter>;

export interface IProjectExternalContoursBoardStore {
  currentProjectId: string;
  currentTab: TInboxIssueCurrentTab;
  error: { message: string; status: "init-error" } | undefined;
  filters: Partial<TExternalContourBoardFilter>;
  items: Record<string, TExternalContourRequest>;
  loader: TLoader;
  sorting: TExternalContourBoardSorting;
  columnIdsMap: Record<TExternalContourBoardDirection, string[]>;
  columnCountMap: Record<TExternalContourBoardDirection, number>;
  tabCountMap: Record<TInboxIssueCurrentTab, number>;
  activeFiltersCount: number;
  fetchBoard: (workspaceSlug: string, projectId: string, tab?: TInboxIssueCurrentTab) => Promise<void>;
  getColumnRequestIds: (direction: TExternalContourBoardDirection) => string[];
  getColumnTotalCount: (direction: TExternalContourBoardDirection) => number;
  getRequestById: (requestId: string) => TExternalContourRequest | undefined;
  handleCurrentTab: (workspaceSlug: string, projectId: string, tab: TInboxIssueCurrentTab) => Promise<void>;
  hasAnyItems: boolean;
  isFiltering: boolean;
  isSortingDefault: boolean;
  clearFilters: (workspaceSlug: string, projectId: string) => Promise<void>;
  replaceFilters: (workspaceSlug: string, projectId: string, filters: Partial<TExternalContourBoardFilter>) => Promise<void>;
  updateFilters: (workspaceSlug: string, projectId: string, filters: Partial<TExternalContourBoardFilter>) => Promise<void>;
  updateSorting: (workspaceSlug: string, projectId: string, sorting: TExternalContourBoardSorting) => Promise<void>;
  upsertBoardItems: (items: TExternalContourRequest[]) => void;
}

export class ProjectExternalContoursBoardStore implements IProjectExternalContoursBoardStore {
  currentProjectId = "";
  currentTab: TInboxIssueCurrentTab = EInboxIssueCurrentTab.OPEN;
  error: { message: string; status: "init-error" } | undefined = undefined;
  filters: Partial<TExternalContourBoardFilter> = {};
  items: Record<string, TExternalContourRequest> = {};
  loader: TLoader = "init-loading";
  sorting: TExternalContourBoardSorting = DEFAULT_SORTING;
  columnIdsMap: Record<TExternalContourBoardDirection, string[]> = {
    outgoing: [],
    incoming: [],
  };
  columnCountMap: Record<TExternalContourBoardDirection, number> = {
    outgoing: 0,
    incoming: 0,
  };
  tabCountMap: Record<TInboxIssueCurrentTab, number> = {
    [EInboxIssueCurrentTab.OPEN]: 0,
    [EInboxIssueCurrentTab.CLOSED]: 0,
  };
  hydratedProjectId = "";
  lastIssuedRequestId = 0;

  externalContourService;

  constructor(private store: CoreRootStore) {
    makeObservable(this, {
      currentProjectId: observable.ref,
      currentTab: observable.ref,
      error: observable.ref,
      filters: observable.ref,
      items: observable,
      loader: observable.ref,
      sorting: observable.ref,
      columnIdsMap: observable,
      columnCountMap: observable,
      tabCountMap: observable,
      activeFiltersCount: computed,
      hasAnyItems: computed,
      isFiltering: computed,
      isSortingDefault: computed,
      clearFilters: action,
      fetchBoard: action,
      handleCurrentTab: action,
      replaceFilters: action,
      updateFilters: action,
      updateSorting: action,
      upsertBoardItems: action,
    });

    this.externalContourService = new ExternalContourService();
  }

  get hasAnyItems() {
    return this.columnIdsMap.outgoing.length > 0 || this.columnIdsMap.incoming.length > 0;
  }

  get isFiltering() {
    return this.loader === "loading";
  }

  get isSortingDefault() {
    return this.sorting.order_by === DEFAULT_SORTING.order_by && this.sorting.sort_by === DEFAULT_SORTING.sort_by;
  }

  get activeFiltersCount() {
    return Object.entries(this.filters).reduce((count, [key, value]) => {
      if (key === "status") return count;
      if (value === undefined || value === null || value === "") return count;
      if (Array.isArray(value)) return count + (value.length > 0 ? 1 : 0);
      if (typeof value === "boolean") return count + (value ? 1 : 0);
      return count + 1;
    }, 0);
  }

  getRequestById = (requestId: string) => this.items[requestId];

  getColumnRequestIds = (direction: TExternalContourBoardDirection) => this.columnIdsMap[direction] ?? [];

  getColumnTotalCount = (direction: TExternalContourBoardDirection) => this.columnCountMap[direction] ?? 0;

  upsertBoardItems = (items: TExternalContourRequest[]) => {
    items.forEach((request) => {
      this.items[request.id] = request;
    });

    this.store.projectExternalContours.upsertRequests(items);
  };

  handleCurrentTab = async (workspaceSlug: string, projectId: string, tab: TInboxIssueCurrentTab) => {
    this.currentTab = tab;
    await this.fetchBoard(workspaceSlug, projectId, tab);
  };

  updateFilters = async (
    workspaceSlug: string,
    projectId: string,
    filters: Partial<TExternalContourBoardFilter>
  ) => {
    this.filters = sanitizeBoardFilters({
      ...this.filters,
      ...filters,
    });

    await this.fetchBoard(workspaceSlug, projectId);
  };

  replaceFilters = async (
    workspaceSlug: string,
    projectId: string,
    filters: Partial<TExternalContourBoardFilter>
  ) => {
    this.filters = sanitizeBoardFilters(filters);
    await this.fetchBoard(workspaceSlug, projectId);
  };

  updateSorting = async (workspaceSlug: string, projectId: string, sorting: TExternalContourBoardSorting) => {
    this.sorting = sorting;
    await this.fetchBoard(workspaceSlug, projectId);
  };

  clearFilters = async (workspaceSlug: string, projectId: string) => {
    this.filters = {};
    this.sorting = DEFAULT_SORTING;
    await this.fetchBoard(workspaceSlug, projectId);
  };

  fetchBoard = async (workspaceSlug: string, projectId: string, tab = this.currentTab) => {
    const hasProjectChanged = !!this.currentProjectId && this.currentProjectId !== projectId;
    const isInitialLoad = this.hydratedProjectId !== projectId;
    const nextFilters = sanitizeBoardFilters(hasProjectChanged ? {} : this.filters);
    const nextSorting = hasProjectChanged ? DEFAULT_SORTING : this.sorting;
    const requestId = ++this.lastIssuedRequestId;

    this.loader = isInitialLoad ? "init-loading" : "loading";
    this.error = undefined;
    if (hasProjectChanged) {
      this.items = {};
      this.columnIdsMap = { outgoing: [], incoming: [] };
      this.columnCountMap = { outgoing: 0, incoming: 0 };
      this.tabCountMap = {
        [EInboxIssueCurrentTab.OPEN]: 0,
        [EInboxIssueCurrentTab.CLOSED]: 0,
      };
    }
    this.currentProjectId = projectId;
    this.currentTab = tab;
    this.filters = nextFilters;
    this.sorting = nextSorting;

    try {
      const response = await this.externalContourService.listBoard(workspaceSlug, projectId, nextFilters, nextSorting);
      if (requestId !== this.lastIssuedRequestId) return;

      runInAction(() => {
        this.columnIdsMap = { outgoing: [], incoming: [] };
        this.columnCountMap = { outgoing: 0, incoming: 0 };
        this.filters = sanitizeBoardFilters(response.filters || nextFilters);
        this.sorting = response.sorting || nextSorting;
        this.hydratedProjectId = projectId;
        let openCount = 0;
        let closedCount = 0;

        response.columns.forEach((column) => {
          this.columnIdsMap[column.key] = column.results.map((request) => request.id);
          this.columnCountMap[column.key] = column.total_count;
          column.results.forEach((request) => {
            if (request.status === EInboxIssueCurrentTab.CLOSED) closedCount += 1;
            else openCount += 1;
          });
          this.upsertBoardItems(column.results);
        });

        this.tabCountMap = {
          [EInboxIssueCurrentTab.OPEN]: openCount,
          [EInboxIssueCurrentTab.CLOSED]: closedCount,
        };

        this.loader = undefined;
      });
    } catch (error: any) {
      if (requestId !== this.lastIssuedRequestId) return;

      runInAction(() => {
        this.loader = undefined;
        this.error = {
          message: error?.error || "Не удалось загрузить доску внешних контуров",
          status: "init-error",
        };
      });
    }
  };
}
