// This program has been developed by students from the bachelor Computer Science at Utrecht
// University within the Software Project course.
// © Copyright Utrecht University (Department of Information and Computing Sciences)
/* contains all logic for the VisProgEditor warning system
*
* Missing but desirable features:
* - Warning filtering:
*     - if there is no completely connected chain of startNode-[PhaseNodes]-EndNode
*       then hide any startNode, phaseNode, or endNode specific warnings
*/
import useFlowStore from "../VisProgStores.tsx";
import type {FlowState} from "../VisProgTypes.tsx";

// --| Type definitions |--

export type WarningId = NodeId | "GLOBAL_WARNINGS";
export type NodeId = string;


export type WarningType =
  | 'MISSING_INPUT'
  | 'MISSING_OUTPUT'
  | 'PLAN_IS_UNDEFINED'
  | 'INCOMPLETE_PROGRAM'
  | 'NOT_CONNECTED_TO_PROGRAM'
  | string

export type WarningSeverity =
  | 'INFO' // Acceptable, but important to be aware of
  | 'WARNING' // Acceptable, but probably undesirable behavior
  | 'ERROR' // Prevents running program, should be fixed before running program is allowed

/**
 * warning scope, include a handleId if the warning is handle specific
 */
export type WarningScope = {
  id: string;
  handleId?: string;
}

export type EditorWarning = {
  scope: WarningScope;
  type: WarningType;
  severity: WarningSeverity;
  description: string;
};

/**
 * a scoped WarningKey,
 * the handleId scoping is only needed for handle specific errors
 *
 * "`WarningType`:`handleId`"
 */
export type WarningKey = string; // for warnings that can occur on a per-handle basis

/**
 * a composite key used in the severityIndex
 *
 * "`WarningId`|`WarningKey`"
 */
export type CompositeWarningKey = string;

export type WarningRegistry = Map<WarningId , Map<WarningKey, EditorWarning>>;
export type SeverityIndex = Map<WarningSeverity, Set<CompositeWarningKey>>;

type ZustandSet = (partial: Partial<FlowState> | ((state: FlowState) => Partial<FlowState>)) => void;
type ZustandGet = () => FlowState;

export type EditorWarningRegistry = {
  /**
   * stores all editor warnings
   */
  editorWarningRegistry: WarningRegistry;
  /**
   * index of warnings by severity
   */
  severityIndex: SeverityIndex;

  /**
   * gets all warnings and returns them as a list of warnings
   * @returns {EditorWarning[]}
   */
  getWarnings: () => EditorWarning[];

  /**
   * gets all warnings with the current severity
   * @param {WarningSeverity} warningSeverity
   * @returns {EditorWarning[]}
   */
  getWarningsBySeverity: (warningSeverity: WarningSeverity) => EditorWarning[];

  /**
   * checks if there are no warnings of breaking severity
   * @returns {boolean}
   */
  isProgramValid: () => boolean;

  /**
   * registers a warning to the warningRegistry and the SeverityIndex
   * @param {EditorWarning} warning
   */
  registerWarning: (warning: EditorWarning) => void;

  /**
   * unregisters a warning from the warningRegistry and the SeverityIndex
   * @param {EditorWarning} warning
   */
  unregisterWarning: (id: WarningId, warningKey: WarningKey) => void

  /**
   * unregisters warnings from the warningRegistry and the SeverityIndex
   * @param {WarningId} warning
   */
  unregisterWarningsForId: (id: WarningId) => void;
}

// --| implemented logic |--

/**
 * the id to use for global editor warnings
 * @type {string}
 */
export const globalWarning = "GLOBAL_WARNINGS";

export function editorWarningRegistry(get: ZustandGet, set: ZustandSet) : EditorWarningRegistry { return {
  editorWarningRegistry: new Map<NodeId, Map<WarningKey, EditorWarning>>(),
  severityIndex: new Map([
    ['INFO', new Set<CompositeWarningKey>()],
    ['WARNING', new Set<CompositeWarningKey>()],
    ['ERROR', new Set<CompositeWarningKey>()],
  ]),

  getWarningsBySeverity: (warningSeverity) => {
    const wRegistry = new Map([...get().editorWarningRegistry].map(([k, v]) => [k, new Map(v)]));
    const sIndex = new Map(get().severityIndex);
    const warningKeys = sIndex.get(warningSeverity);
    const warnings: EditorWarning[] = [];

    warningKeys?.forEach(
      (compositeKey) => {
        const [id, warningKey] = compositeKey.split('|');
        const warning = wRegistry.get(id)?.get(warningKey);

        if (warning) {
          warnings.push(warning);
        }
      }
    )

    return warnings;
  },

  isProgramValid: () => {
    const sIndex = get().severityIndex;
    return (sIndex.get("ERROR")!.size === 0);
  },

  getWarnings: () => Array.from(get().editorWarningRegistry.values())
      .flatMap(innerMap => Array.from(innerMap.values())),


  registerWarning: (warning) => {
    const { scope: {id, handleId}, type, severity } = warning;
    const warningKey = handleId ? `${type}:${handleId}` : type;
    const compositeKey = `${id}|${warningKey}`;
    const wRegistry = new Map([...get().editorWarningRegistry].map(([k, v]) => [k, new Map(v)]));
    const sIndex = new Map(get().severityIndex);
    // add to warning registry
    if (!wRegistry.has(id)) {
      wRegistry.set(id, new Map());
    }
    wRegistry.get(id)!.set(warningKey, warning);


    // add to severityIndex
    if (!sIndex.get(severity)!.has(compositeKey)) {
      sIndex.get(severity)!.add(compositeKey);
    }

    set({
      editorWarningRegistry: wRegistry,
      severityIndex: sIndex
    })
  },

  unregisterWarning: (id, warningKey) => {
    const wRegistry = new Map([...get().editorWarningRegistry].map(([k, v]) => [k, new Map(v)]));
    const sIndex = new Map(get().severityIndex);
    // verify if the warning was created already
    const warning = wRegistry.get(id)?.get(warningKey);
    if (!warning) return;

    // remove from warning registry
    wRegistry.get(id)!.delete(warningKey);


    // remove from severityIndex
    sIndex.get(warning.severity)!.delete(`${id}|${warningKey}`);

    set({
      editorWarningRegistry: wRegistry,
      severityIndex: sIndex
    })
  },

  unregisterWarningsForId: (id) => {
    const wRegistry = new Map([...get().editorWarningRegistry].map(([k, v]) => [k, new Map(v)]));
    const sIndex = new Map(get().severityIndex);

    const nodeWarnings = wRegistry.get(id);

    // remove from severity index
    if (nodeWarnings) {
      nodeWarnings.forEach((warning) => {
        const warningKey = warning.scope.handleId
          ? `${warning.type}:${warning.scope.handleId}`
          : warning.type;
        sIndex.get(warning.severity)?.delete(`${id}|${warningKey}`);
      });
    }

    // remove from warning registry
    wRegistry.delete(id);

    set({
      editorWarningRegistry: wRegistry,
      severityIndex: sIndex
    })
  },
}}



/**
 * returns a summary of the warningRegistry
 * @returns {{info: number, warning: number, error: number, isValid: boolean}}
 */
export function warningSummary() {
  const {severityIndex, isProgramValid} = useFlowStore.getState();
  return {
    info: severityIndex.get('INFO')!.size,
    warning: severityIndex.get('WARNING')!.size,
    error: severityIndex.get('ERROR')!.size,
    isValid: isProgramValid(),
  };
}


