import * as plugins from '../../plugins.js';
import { SimpleLocalClient } from '../../core/index.js';
import { FolderWatcherMapper } from './folder_watcher.mapper.js';
import type { IFolderWatcherConfig, IFolderWatcherSnapshot } from './folder_watcher.types.js';
import { folderWatcherDefaultPattern, folderWatcherProfile } from './folder_watcher.types.js';

export class FolderWatcherClient extends SimpleLocalClient<IFolderWatcherConfig> {
  constructor(private readonly configArg: IFolderWatcherConfig) {
    super(folderWatcherProfile, configArg);
  }

  public async getSnapshot(forceRefreshArg = false): Promise<IFolderWatcherSnapshot> {
    if (this.shouldReadLocalFolder()) {
      try {
        return FolderWatcherMapper.toSnapshot({ config: this.configArg, rawData: await this.readLocalFolder(), online: true, source: 'manual' });
      } catch (errorArg) {
        return FolderWatcherMapper.toSnapshot({ config: this.configArg, online: false, source: 'runtime', error: errorMessage(errorArg) });
      }
    }

    const snapshot = await super.getSnapshot(forceRefreshArg);
    if (snapshot.rawData === undefined && snapshot.entities.length) {
      return FolderWatcherMapper.toSnapshot({ config: { ...this.configArg, snapshot }, online: snapshot.online, source: snapshot.source, error: snapshot.error });
    }

    return FolderWatcherMapper.toSnapshot({ config: this.configArg, rawData: snapshot.rawData, online: snapshot.online, source: snapshot.source, error: snapshot.error });
  }

  private shouldReadLocalFolder(): boolean {
    return Boolean(this.folderPath() && !this.configArg.snapshot && this.configArg.rawData === undefined && this.configArg.rawText === undefined && !this.configArg.entities?.length && !this.configArg.client && !this.configArg.snapshotProvider && !this.configArg.host);
  }

  private async readLocalFolder(): Promise<Record<string, unknown>> {
    const folderPath = this.folderPath();
    if (!folderPath) {
      throw new Error('Folder Watcher snapshots require config.folder, config.folderPath, or config.folder_path.');
    }

    const patterns = this.patterns();
    const entries = await plugins.fs.readdir(folderPath, { withFileTypes: true });
    const watchedFiles = entries
      .filter((entryArg) => entryArg.isFile() && patterns.some((patternArg) => matchesFilter(entryArg.name, patternArg)))
      .map((entryArg) => plugins.path.join(folderPath, entryArg.name));

    return {
      folder: folderPath,
      patterns,
      watched_files: watchedFiles.length,
      file_list: watchedFiles,
      last_event: null,
    };
  }

  private folderPath(): string | undefined {
    const metadata = this.configArg.metadata;
    return stringValue(this.configArg.folder) || stringValue(this.configArg.folderPath) || stringValue(this.configArg.folder_path) || stringValue(this.configArg.path) || stringValue(metadata?.folder) || stringValue(metadata?.folderPath) || stringValue(metadata?.folder_path);
  }

  private patterns(): string[] {
    const metadata = this.configArg.metadata;
    const patterns = stringArray(this.configArg.patterns) || stringArray(metadata?.patterns);
    return patterns?.length ? patterns : [folderWatcherDefaultPattern];
  }
}

const matchesFilter = (nameArg: string, filterArg: string): boolean => {
  const escaped = filterArg.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.');
  return new RegExp(`^${escaped}$`).test(nameArg);
};

const stringArray = (valueArg: unknown): string[] | undefined => Array.isArray(valueArg) ? valueArg.filter((itemArg): itemArg is string => typeof itemArg === 'string' && itemArg.trim().length > 0) : undefined;

const stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;

const errorMessage = (errorArg: unknown): string => errorArg instanceof Error ? errorArg.message : String(errorArg);
