/**
 * Copyright (c) 2020-present, Goldman Sachs
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {
  action,
  computed,
  flow,
  flowResult,
  makeObservable,
  observable,
} from 'mobx';
import {
  type EditorStore,
  DataProductEditorState,
  ElementEditorState,
} from '@finos/legend-application-studio';
import {
  type DataProduct,
  type PackageableElement,
  Package,
  Class,
  Enumeration,
  Association,
  Service,
  ConcreteFunctionDefinition,
  observe_DataProduct,
} from '@finos/legend-graph';
import { Diagram } from '@finos/legend-extension-dsl-diagram/graph';
import {
  DataSpace,
  DataSpacePackageableElementExecutable,
  type DataSpaceElement,
} from '@finos/legend-extension-dsl-data-space/graph';
import {
  assertErrorThrown,
  guaranteeType,
  type GeneratorFn,
} from '@finos/legend-shared';
import { DataSpaceExecutionContextState } from './DataSpaceExecutionContextState.js';
import { convertDataSpaceToDataProduct } from '../stores/DataSpaceToDataProductConverter.js';
import { DSL_DATA_SPACE_LEGEND_STUDIO_APPLICATION_LOGGING_CONTEXT_KEY } from '../__lib__/DSL_DataSpace_LegendStudioDocumentation.js';

export const onConvertDataSpaceToDataProduct = flow(function* (
  dataSpace: DataSpace,
  editorStore: EditorStore,
  dataSpaceEditorState: DataSpaceEditorState,
): GeneratorFn<void> {
  try {
    const dataProduct = convertDataSpaceToDataProduct(dataSpace);

    editorStore.graphManagerState.graph.deleteElement(dataSpace);

    editorStore.graphManagerState.graph.addElement(
      dataProduct,
      dataSpace.package?.path.replace(/dataspace/, 'dataProduct'),
    );

    const dataSpacePackage = dataSpace.package;
    if (dataSpacePackage && dataSpacePackage.children.length === 0) {
      editorStore.graphManagerState.graph.deleteElement(dataSpacePackage);
    }
    const addedElement = editorStore.graphManagerState.graph.getNullableElement(
      dataProduct.path,
    );
    if (addedElement) {
      observe_DataProduct(addedElement as DataProduct);
    }
    const dataProductEditorState = new DataProductEditorState(
      editorStore,
      addedElement as DataProduct,
    );

    editorStore.tabManagerState.closeTab(dataSpaceEditorState);
    editorStore.tabManagerState.openTab(dataProductEditorState);
    yield flowResult(editorStore.explorerTreeState.build());
    editorStore.applicationStore.telemetryService.logEvent(
      DSL_DATA_SPACE_LEGEND_STUDIO_APPLICATION_LOGGING_CONTEXT_KEY.CONVERT_DATA_SPACE_TO_DATA_PRODUCT,
      {
        sourceInfo: editorStore.editorMode.getSourceInfo(),
        dataSpacePath: dataSpace.path,
        dataProductPath: dataProduct.path,
      },
    );

    editorStore.applicationStore.notificationService.notifySuccess(
      `Successfully converted DataSpace ${dataSpace.name} to Data Product`,
    );
  } catch (error) {
    assertErrorThrown(error);
    editorStore.applicationStore.notificationService.notifyError(
      `Failed to convert DataSpace to Data Product: ${error.message}`,
    );
  }
});

export class DataSpaceEditorState extends ElementEditorState {
  executionContextState: DataSpaceExecutionContextState;

  constructor(editorStore: EditorStore, element: PackageableElement) {
    super(editorStore, element);

    makeObservable(this, {
      executionContextState: observable,
      dataSpace: computed,
      reprocess: action,
      isValidDataSpaceElement: action,
      getDataSpaceElementOptions: action,
      getDiagramOptions: action,
      getDataSpaceExecutableOptions: action,
    });

    this.executionContextState = new DataSpaceExecutionContextState(this);
  }

  isValidDataSpaceElement(
    element: PackageableElement,
  ): element is DataSpaceElement {
    return (
      element instanceof Package ||
      element instanceof Class ||
      element instanceof Enumeration ||
      element instanceof Association
    );
  }

  getDataSpaceElementOptions(): { label: string; value: DataSpaceElement }[] {
    const currentElements =
      this.dataSpace.elements?.map(
        (elementPointer) => elementPointer.element.value,
      ) ?? [];
    return this.editorStore.graphManagerState.graph.allOwnElements
      .filter((element) => this.isValidDataSpaceElement(element))
      .filter((element) => !currentElements.includes(element))
      .map((element) => ({
        label: element.path,
        value: element,
      }));
  }

  getDataSpaceExecutableOptions(): {
    label: string;
    value: PackageableElement;
  }[] {
    const currentExecutables =
      this.dataSpace.executables?.map((executablePointer) => {
        if (
          executablePointer instanceof DataSpacePackageableElementExecutable
        ) {
          return executablePointer.executable.value;
        }
        return undefined;
      }) ?? [];
    return this.editorStore.graphManagerState.graph.allOwnElements
      .filter(
        (element) =>
          element instanceof Service ||
          element instanceof ConcreteFunctionDefinition,
      )
      .filter((executable) => !currentExecutables.includes(executable))
      .map((executable) => ({
        label: executable.path,
        value: executable,
      }));
  }

  getDiagramOptions(): { label: string; value: Diagram }[] {
    const currentDiagrams =
      this.dataSpace.diagrams?.map(
        (diagramPointer) => diagramPointer.diagram.value,
      ) ?? [];
    return this.editorStore.graphManagerState.graph.allOwnElements
      .filter((element): element is Diagram => element instanceof Diagram)
      .filter((diagram) => !currentDiagrams.includes(diagram))
      .map((diagram) => ({
        label: diagram.path,
        value: diagram,
      }));
  }

  get dataSpace(): DataSpace {
    return guaranteeType(
      this.element,
      DataSpace,
      'Element inside DataSpace editor state must be a DataSpace element',
    );
  }

  override reprocess(
    newElement: PackageableElement,
    editorStore: EditorStore,
  ): ElementEditorState {
    const newState = new DataSpaceEditorState(editorStore, newElement);
    return newState;
  }
}
