/**
 * 自动提交管理器
 *
 * 参考 aider 的 auto_commit 实现
 * 支持 immediate、batch、manual 三种模式
 */

import { minimatch } from 'minimatch';
import type { GitRepo } from './repo.js';
import type { AutoCommitConfig, CommitResult } from './types.js';
import { MessageGenerator } from './message-generator.js';

export class AutoCommitManager {
  private repo: GitRepo;
  private config: AutoCommitConfig;
  private messageGenerator: MessageGenerator;

  /** 待提交的文件 */
  private pendingFiles: Set<string> = new Set();

  /** 批量提交定时器 */
  private batchTimer: NodeJS.Timeout | null = null;

  /** 提交回调 */
  private onCommitCallback?: (result: CommitResult) => void;

  constructor(repo: GitRepo, config: AutoCommitConfig, messageGenerator: MessageGenerator) {
    this.repo = repo;
    this.config = config;
    this.messageGenerator = messageGenerator;
  }

  /**
   * 设置提交回调
   */
  setOnCommit(callback: (result: CommitResult) => void): void {
    this.onCommitCallback = callback;
  }

  /**
   * 文件变更后调用
   */
  async onFileChanged(filePath: string, changeType: 'create' | 'modify' | 'delete'): Promise<void> {
    if (!this.config.enabled) {
      return;
    }

    // 检查是否应该排除
    if (this.shouldExclude(filePath)) {
      return;
    }

    // 如果启用脏文件提交，检查文件是否为脏状态
    if (this.config.dirtyCommits && changeType !== 'create') {
      const isDirty = await this.repo.isDirty(filePath);
      if (isDirty) {
        // 在新的编辑之前，先提交脏文件
        await this.commitDirtyFile(filePath);
      }
    }

    // 添加到待提交列表
    this.pendingFiles.add(filePath);

    // 根据模式处理
    switch (this.config.mode) {
      case 'immediate':
        await this.executeCommit();
        break;

      case 'batch':
        this.scheduleBatchCommit();
        break;

      case 'manual':
        // 不自动提交，只记录
        break;
    }
  }

  /**
   * 计划批量提交
   */
  private scheduleBatchCommit(): void {
    if (this.batchTimer) {
      clearTimeout(this.batchTimer);
    }

    this.batchTimer = setTimeout(async () => {
      this.batchTimer = null;
      await this.executeCommit();
    }, this.config.batchDelay);
  }

  /**
   * 执行提交
   */
  private async executeCommit(): Promise<CommitResult | null> {
    if (this.pendingFiles.size === 0) {
      return null;
    }

    const files = Array.from(this.pendingFiles);
    this.pendingFiles.clear();

    try {
      // 获取差异用于生成消息
      const diff = await this.repo.getDiff({ staged: false });

      // 生成提交消息
      const message = this.messageGenerator.generate(diff, files);

      // 执行提交
      const result = await this.repo.commit({
        files,
        message,
        aiEdits: true,
      });

      // 调用回调
      if (result.success && this.onCommitCallback) {
        this.onCommitCallback(result);
      }

      return result;
    } catch (error) {
      // 恢复 pending 状态
      files.forEach((f) => this.pendingFiles.add(f));

      return {
        success: false,
        error: error instanceof Error ? error.message : String(error),
      };
    }
  }

  /**
   * 提交脏文件
   */
  private async commitDirtyFile(filePath: string): Promise<void> {
    try {
      await this.repo.commit({
        files: [filePath],
        message: `chore: save changes to ${filePath} before AI edit`,
        aiEdits: false, // 用户的脏文件，不标记为 AI 编辑
      });
    } catch {
      // 脏文件提交失败不影响主流程
    }
  }

  /**
   * 检查文件是否应该排除
   */
  private shouldExclude(filePath: string): boolean {
    return this.config.excludePatterns.some((pattern) =>
      minimatch(filePath, pattern, { matchBase: true })
    );
  }

  /**
   * 强制立即提交
   */
  async flush(): Promise<CommitResult | null> {
    if (this.batchTimer) {
      clearTimeout(this.batchTimer);
      this.batchTimer = null;
    }
    return this.executeCommit();
  }

  /**
   * 获取待提交文件
   */
  getPendingFiles(): string[] {
    return Array.from(this.pendingFiles);
  }

  /**
   * 清除待提交文件
   */
  clearPending(): void {
    if (this.batchTimer) {
      clearTimeout(this.batchTimer);
      this.batchTimer = null;
    }
    this.pendingFiles.clear();
  }

  /**
   * 是否有待提交文件
   */
  hasPendingFiles(): boolean {
    return this.pendingFiles.size > 0;
  }
}
