/*
 * Copyright (c) 2025-2026 Huawei Device Co., Ltd.
 * 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 { KNativePointer } from './InteropTypes';
import { BuildConfig } from './types';

export enum PluginHook {
  NEW = 'afterNew',
  PARSED = 'parsed',
  SCOPE_INITED = 'scopeInited',
  CHECKED = 'checked',
  LOWERED = 'lowered',
  ASM_GENERATED = 'asmGenerated',
  BIN_GENERATED = 'binGenerated',
  CLEAN = 'clean'
}

type PluginHandlerFunction = () => void;

type PluginHandlerObject = {
  order: 'pre' | 'post' | undefined;
  handler: PluginHandlerFunction;
};

type PluginHandler = PluginHandlerFunction | PluginHandlerObject;

interface Plugins {
  name: string;
  afterNew?: PluginHandler;
  parsed?: PluginHandler;
  scopeInited?: PluginHandler;
  checked?: PluginHandler;
  lowered?: PluginHandler;
  asmGenerated?: PluginHandler;
  binGenerated?: PluginHandler;
  clean?: PluginHandler;
}

type PluginExecutor = {
  name: string;
  handler: PluginHandler;
};

type PluginInitFunction = () => Plugins;

type RawPlugins = {
  name: string;
  init: PluginInitFunction | undefined;
};

class PluginContext {
  private ast: object | undefined;
  private program: object | undefined;
  private projectConfig: object | undefined;
  private contextPtr: KNativePointer | undefined;
  private codingFilePath: string | undefined;

  constructor() {
    this.ast = undefined;
    this.program = undefined;
    this.projectConfig = undefined;
    this.contextPtr = undefined;
    this.codingFilePath = undefined;
  }

  public setArkTSAst(ast: object): void {
    this.ast = ast;
  }

  public getArkTSAst(): object | undefined {
    return this.ast;
  }

  public setArkTSProgram(program: object): void {
    this.program = program;
  }

  public getArkTSProgram(): object | undefined {
    return this.program;
  }

  public setProjectConfig(projectConfig: object): void {
    this.projectConfig = projectConfig;
  }

  public getProjectConfig(): object | undefined {
    return this.projectConfig;
  }

  public setContextPtr(ptr: KNativePointer): void {
    this.contextPtr = ptr;
  }

  public getContextPtr(): KNativePointer | undefined {
    return this.contextPtr;
  }

  public setCodingFilePath(codingFilePath: string): void {
    this.codingFilePath = codingFilePath;
  }

  public getCodingFilePath(): string | undefined {
    return this.codingFilePath;
  }

  public isCoding(): boolean {
    return this.codingFilePath !== undefined;
  }
}

export class PluginDriver {
  private static instance: PluginDriver | undefined;
  private sortedPlugins: Map<PluginHook, PluginExecutor[] | undefined>;
  private allPlugins: Map<string, Plugins>;
  private context: PluginContext;

  constructor() {
    this.sortedPlugins = new Map<PluginHook, PluginExecutor[] | undefined>();
    this.allPlugins = new Map<string, Plugins>();
    this.context = new PluginContext();
  }

  public static getInstance(): PluginDriver {
    if (!this.instance) {
      this.instance = new PluginDriver();
    }
    return this.instance;
  }

  public static destroyInstance(): void {
    PluginDriver.instance = undefined;
  }

  public initPlugins(projectConfig: BuildConfig): void {
    this.allPlugins.clear();
    const pluginResults: RawPlugins[] = Object.entries(projectConfig.plugins).map(([key, value]) => {
      let pluginObject = require(value as string);
      let initFunction = Object.values(pluginObject)[0] as PluginInitFunction;
      return {
        name: key,
        init: initFunction
      };
    });

    pluginResults.forEach((plugin: RawPlugins) => {
      if (plugin.init !== undefined) {
        this.allPlugins.set(plugin.name, plugin.init());
      }
    });

    this.context.setProjectConfig(projectConfig);
  }

  private getPlugins(hook: PluginHook): PluginExecutor[] | undefined {
    if (!this.sortedPlugins.has(hook)) {
      const sortedPlugins: PluginExecutor[] = this.getSortedPlugins(hook);
      if (sortedPlugins.length === 0) {
        this.sortedPlugins.set(hook, undefined);
      } else {
        this.sortedPlugins.set(hook, sortedPlugins);
      }
    }

    return this.sortedPlugins.get(hook);
  }

  private getSortedPlugins(hook: PluginHook): PluginExecutor[] {
    let pre: PluginExecutor[] = [];
    let normal: PluginExecutor[] = [];
    let post: PluginExecutor[] = [];

    this.allPlugins.forEach((pluginObject: Plugins, name: string) => {
      if (!pluginObject[hook]) {
        return;
      }

      let pluginName: string = pluginObject.name;
      let handler: PluginHandler = pluginObject[hook]!;
      let order: string | undefined = typeof handler === 'object' ? handler.order : undefined;

      let rawPluginHook: PluginExecutor = {
        name: pluginName,
        handler: typeof handler === 'object' ? handler.handler : handler
      };

      if (order === 'pre') {
        pre.push(rawPluginHook);
      } else if (order === 'post') {
        post.push(rawPluginHook);
      } else {
        normal.push(rawPluginHook);
      }
    });

    return [...pre, ...normal, ...post];
  }

  public runPluginHook(hook: PluginHook): void {
    let plugins: PluginExecutor[] | undefined = this.getPlugins(hook);
    if (!plugins) {
      return;
    }
    plugins.forEach((executor: PluginExecutor) => {
      return (executor.handler as Function).apply(this.context);
    });
  }

  public getPluginContext(): PluginContext {
    return this.context;
  }
}