import { Logger } from "../utils";
import * as vscode from "vscode";
import type { GitExtension, Repository } from "../types/git";
import type { LanguageClient } from "vscode-languageclient/node";
import type { SidebarProvider } from "../providers/SidebarProvider";
import { LSP_COMMANDS } from "../constants";
import { ConfigKey, type ConfigService } from "./configService";

export class GitWatcherService {
  private logger: Logger;
  private repository: Repository | null = null;
  private disposables: vscode.Disposable[] = [];
  private lastCommit: string | undefined;

  constructor(
    private readonly sidebarProvider: SidebarProvider,
    private readonly lspClient: LanguageClient,
    private readonly configService: ConfigService,
  ) {
    this.logger = new Logger("GitWatcherService");
    this.sidebarProvider = sidebarProvider;
    this.lspClient = lspClient;

    const gitExtension =
      vscode.extensions.getExtension<GitExtension>("vscode.git")?.exports;
    if (!gitExtension) {
      this.logger.error("Git extension not found");
      return;
    }

    const api = gitExtension.getAPI(1);
    if (api.repositories.length !== 1) {
      this.logger.warn(
        "Multiple repositories found, only supporting one repository",
      );
      return;
    }

    this.repository = api.repositories[0]!;

    const disposable = this.repository.state.onDidChange(() => {
      this.onHeadChanged();
    });
    this.disposables.push(disposable);
  }

  private async onHeadChanged() {
    if (!this.repository) {
      return;
    }

    const head = this.repository.state.HEAD;
    if (!head?.commit) {
      return;
    }

    const newHeadCommit = head.commit;

    if (this.lastCommit && this.lastCommit !== newHeadCommit) {
      const bestCommonAncestor = await this.repository.getMergeBase(
        this.lastCommit,
        newHeadCommit,
      );
      if (bestCommonAncestor === this.lastCommit) {
        // this means the last commit is the parent of the new commit
        this.handleNewCommit(newHeadCommit);
      }
    }

    this.lastCommit = newHeadCommit;
  }

  private handleNewCommit(commit: string) {
    if (!this.configService.get(ConfigKey.GitHookEnabled)) {
      return;
    }

    this.lspClient
      .sendRequest<{
        status: string;
        functions: Record<string, string[]>;
      }>(LSP_COMMANDS.GET_OPTIMIZABLE_FUNCTIONS_IN_COMMIT, {
        commit_hash: commit,
      })
      .then((result) => result.functions)
      .then((funcs) => this.addCommitFunctionsToQueue(funcs));
  }

  private addCommitFunctionsToQueue(functions: Record<string, string[]>): void {
    const totalFunctionsCount = Object.values(functions).reduce(
      (acc, val) => acc + val.length,
      0,
    );
    vscode.window.showInformationMessage(
      `Found ${totalFunctionsCount} optimizable functions in the commit, adding to optimization queue...`,
    );
    for (const [path, qualifiedNames] of Object.entries(functions)) {
      for (const name of qualifiedNames) {
        this.sidebarProvider.addFunctionToQueue(name, vscode.Uri.file(path));
      }
    }
  }
  dispose() {
    this.disposables.forEach((d) => d.dispose());
  }
}
