/* eslint-disable @typescript-eslint/no-namespace */
import { Logger } from '@nestjs/common';
import { IssueEnhancerInterface } from '../issue-enhancers/issue-enhancer-interface';
import { RedmineTypes } from '../models/redmine-types';
import { FlatIssuesStore, FlatIssuesStoreNs } from './flat-issues-store';

export namespace TreeIssuesStoreNs {
  export namespace Models {
    export namespace GetFlatStories {
      export type Item = {
        metainfo: Record<string, any>;
        store: FlatIssuesStore;
      };

      export type Result = Item[];
    }
  }
}

export class TreeIssuesStore {
  private logger = new Logger(TreeIssuesStore.name);
  private rootIssue: RedmineTypes.Issue;
  private flatStore: FlatIssuesStore;

  setRootIssue(issue: RedmineTypes.Issue): void {
    this.rootIssue = issue;
    this.prepareFlatIssuesStore();
    this.logger.debug(`Set root issue_id - ${JSON.stringify(issue.id)}`);
  }

  async fillData(loader: FlatIssuesStoreNs.IssuesLoader): Promise<void> {
    await this.flatStore.fillData(loader);
  }

  async enhanceIssues(enhancers: IssueEnhancerInterface[]): Promise<void> {
    await this.flatStore.enhanceIssues(enhancers);
  }

  getFlatStore(): FlatIssuesStore {
    return this.flatStore;
  }

  private prepareFlatIssuesStore(): void {
    this.flatStore = new FlatIssuesStore();
    this.flatStore.push(this.rootIssue);
    if (this.rootIssue.children && this.rootIssue.children.length > 0) {
      this.fillChildrenFlatIssuesStore(this.rootIssue.children);
    }
  }

  private fillChildrenFlatIssuesStore(
    childrenIssues: RedmineTypes.Children,
  ): void {
    for (let i = 0; i < childrenIssues.length; i++) {
      const issue = childrenIssues[i];
      this.flatStore.push(issue.id);
      if (issue.children && issue.children.length > 0) {
        this.fillChildrenFlatIssuesStore(issue.children);
      }
    }
  }

  isFullLoaded(): boolean {
    return this.flatStore.isFullLoaded();
  }

  getFlatStories(
    fromIssues: number[],
    fromIssuesIncluded: boolean,
    showOthers: boolean,
  ): TreeIssuesStoreNs.Models.GetFlatStories.Result {
    const res = [] as TreeIssuesStoreNs.Models.GetFlatStories.Result;

    for (let i = 0; i < fromIssues.length; i++) {
      const fromIssue = this.flatStore.getIssue(fromIssues[i]);
      if (!fromIssue.data) continue;
      const store = new FlatIssuesStore();
      this.putIssuesToStore(
        store,
        fromIssue.data,
        fromIssues,
        fromIssuesIncluded,
      );
      res.push({
        store: store,
        metainfo: this.createMetaInfo(fromIssue.data),
      });
    }

    if (showOthers) {
      const othersStore = new FlatIssuesStore();
      res.push({
        store: othersStore,
        metainfo: this.createMetaInfo(this.rootIssue),
      });
      this.putIssuesToStore(
        othersStore,
        this.rootIssue,
        fromIssues,
        fromIssuesIncluded,
      );
    }

    return res;
  }

  private putIssuesToStore(
    store: FlatIssuesStore,
    rootIssue: RedmineTypes.Issue,
    fromIssues: number[],
    fromIssuesIncluded: boolean,
  ): void {
    if (fromIssuesIncluded) {
      store.push(rootIssue);
    }
    if (!rootIssue.children || rootIssue.children.length <= 0) {
      return;
    }
    for (let i = 0; i < rootIssue.children.length; i++) {
      const childIssue = rootIssue.children[i];
      const issueData = this.flatStore.getIssue(childIssue.id);
      if (!issueData.data) continue;
      if (fromIssues.indexOf(issueData.data.id) < 0) {
        store.push(issueData.data);
        this.putIssuesToStore(
          store,
          issueData.data,
          fromIssues,
          fromIssuesIncluded,
        );
      } else {
        if (fromIssuesIncluded == false) {
          store.push(issueData.data);
        }
      }
    }
  }

  private createMetaInfo(issue: RedmineTypes.Issue): Record<string, any> {
    return {
      title: `${issue.tracker.name} #${issue.id} - ${issue.subject}`,
      rootIssue: {
        id: issue.id,
        tracker: issue.tracker,
        subject: issue.subject,
      },
    };
  }

  getParents(issue: RedmineTypes.Issue): RedmineTypes.Issue[] {
    const res = [] as RedmineTypes.Issue[];
    let parentId: number;
    let parentIssueData: FlatIssuesStoreNs.Models.FindResult;
    let parentIssue: RedmineTypes.Issue;
    parentId = issue.parent?.id;
    while (parentId) {
      parentIssueData = this.flatStore.getIssue(parentId);
      parentIssue = parentIssueData.data;
      if (!parentIssue) break;
      res.push(parentIssue);
      parentId = parentIssue.parent?.id;
    }
    return res;
  }

  getFlatStoriesByParents(): TreeIssuesStoreNs.Models.GetFlatStories.Result {
    const stories = [] as TreeIssuesStoreNs.Models.GetFlatStories.Result;
    return this.fillFlatStoriesByParents(stories, this.rootIssue);
  }

  private fillFlatStoriesByParents(
    stories: TreeIssuesStoreNs.Models.GetFlatStories.Result,
    rootIssue: RedmineTypes.Issue,
  ): TreeIssuesStoreNs.Models.GetFlatStories.Result {
    if (!rootIssue.children || rootIssue.children.length <= 0) {
      return stories;
    }
    const store = new FlatIssuesStore();
    stories.push({
      store: store,
      metainfo: this.createMetaInfo(rootIssue),
    });
    for (let i = 0; i < rootIssue.children.length; i++) {
      const child = rootIssue.children[i];
      const childIssueData = this.flatStore.getIssue(child.id);
      if (!childIssueData.data) continue;
      store.push(childIssueData.data);
      this.fillFlatStoriesByParents(stories, childIssueData.data);
    }
    return stories;
  }

  getIssuesWithChildren(): RedmineTypes.Issue[] {
    return this.fillIssuesWithChildren(this.rootIssue, []);
  }

  private fillIssuesWithChildren(
    issue: RedmineTypes.Issue,
    data: RedmineTypes.Issue[],
  ): RedmineTypes.Issue[] {
    if (!issue || !issue.children || issue.children.length <= 0) return;
    data.push(issue);
    for (let i = 0; i < issue.children.length; i++) {
      const childIssueResult = this.getFlatStore().getIssue(
        issue.children[i].id,
      );
      if (!childIssueResult || !childIssueResult.data) continue;
      const childIssue = childIssueResult.data;
      this.fillIssuesWithChildren(childIssue, data);
    }
    return data;
  }
}
