/**
 * 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 { setErrorMarkers } from '@finos/legend-code-editor';
import {
  _lambda,
  DataCubeCodeEditorState,
  type DataCubeEngine,
  type DataCubeAlertService,
  type DataCubeSource,
} from '@finos/legend-data-cube';
import { EngineError, type V1_Lambda } from '@finos/legend-graph';
import {
  ActionState,
  assertErrorThrown,
  uuid,
  type PlainObject,
} from '@finos/legend-shared';
import { action, makeObservable, observable } from 'mobx';

export class LegendDataCubeCodeEditorState extends DataCubeCodeEditorState {
  protected override readonly uuid = uuid();
  readonly validationState = ActionState.create();
  readonly alertService: DataCubeAlertService;

  queryLambda: () => V1_Lambda;

  constructor(
    engine: DataCubeEngine,
    alertService: DataCubeAlertService,
    model: DataCubeSource | PlainObject | undefined,
  ) {
    super(engine);
    makeObservable(this, {
      editor: observable.ref,
      setEditor: action,

      codeError: observable.ref,
      showError: action,
      clearError: action,

      returnType: observable,
      setReturnType: action,
    });

    this.engine = engine;
    this.alertService = alertService;
    this.model = model;
    this.queryLambda = this.buildDataCubeQuery;
  }

  alertHandler = (error: Error): void => {
    this.alertService.alertUnhandledError(error);
  };

  initialize(query: string) {
    this.code = query;
    this.editorModel.setValue(this.code);
  }

  setModel(val: PlainObject | undefined) {
    this.model = val;
  }

  showError(error: EngineError) {
    this.codeError = error;
    if (error.sourceInformation) {
      setErrorMarkers(
        this.editorModel,
        [
          {
            message: error.message,
            startLineNumber: error.sourceInformation.startLine,
            startColumn: error.sourceInformation.startColumn,
            endLineNumber: error.sourceInformation.endLine,
            endColumn: error.sourceInformation.endColumn,
          },
        ],
        this.uuid,
      );
    }
  }

  buildDataCubeQuery = () => {
    let parsedLambda: V1_Lambda = _lambda([], []);
    this.engine
      .parseValueSpecification(this.code)
      .then((lambda) => {
        parsedLambda = _lambda([], [lambda]);
      })
      .catch((error) => {
        assertErrorThrown(error);
        if (error instanceof EngineError) {
          this.validationState.fail();
          // correct the source information since we added prefix to the code
          // and reveal error in the editor
          if (error.sourceInformation) {
            error.sourceInformation.startColumn -=
              error.sourceInformation.startLine === 1
                ? this.codePrefix.length
                : 0;
            error.sourceInformation.endColumn -=
              error.sourceInformation.endLine === 1
                ? this.codePrefix.length
                : 0;
            const fullRange = this.editorModel.getFullModelRange();
            if (
              error.sourceInformation.startLine < 1 ||
              (error.sourceInformation.startLine === 1 &&
                error.sourceInformation.startColumn < 1) ||
              error.sourceInformation.endLine > fullRange.endLineNumber ||
              (error.sourceInformation.endLine === fullRange.endLineNumber &&
                error.sourceInformation.endColumn > fullRange.endColumn)
            ) {
              error.sourceInformation.startColumn = fullRange.startColumn;
              error.sourceInformation.startLine = fullRange.startLineNumber;
              error.sourceInformation.endColumn = fullRange.endColumn;
              error.sourceInformation.endLine = fullRange.endLineNumber;
            }
          }
          this.showError(error);
        }
      });
    return parsedLambda;
  };

  async getReturnType() {
    this.validationState.inProgress();

    // properly reset the error state before revalidating
    this.clearError();
    this.setReturnType(undefined);

    try {
      this.buildDataCubeQuery();
      return undefined;
    } catch (error) {
      assertErrorThrown(error);
      this.alertHandler(error);
    } finally {
      this.validationState.complete();
    }

    return undefined;
  }

  close() {
    // dispose the editor and its model to avoid memory leak
    this.editorModel.dispose();
    this.editor?.dispose();
  }
}
