import * as plugins from '../../plugins.js';
import { SimpleLocalClient } from '../../core/index.js';
import { FolderMapper } from './folder.mapper.js';
import type { IFolderConfig, IFolderSnapshot } from './folder.types.js';
import { folderDefaultFilter, folderProfile } from './folder.types.js';

export class FolderClient extends SimpleLocalClient<IFolderConfig> {
  constructor(private readonly configArg: IFolderConfig) {
    super(folderProfile, configArg);
  }

  public async getSnapshot(forceRefreshArg = false): Promise<IFolderSnapshot> {
    if (this.shouldReadLocalFolder()) {
      try {
        return FolderMapper.toSnapshot({ config: this.configArg, rawData: await this.readLocalFolder(), online: true, source: 'manual' });
      } catch (errorArg) {
        return FolderMapper.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 FolderMapper.toSnapshot({ config: { ...this.configArg, snapshot }, online: snapshot.online, source: snapshot.source, error: snapshot.error });
    }

    return FolderMapper.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 snapshots require config.folder, config.folderPath, or config.folder_path.');
    }

    const filter = this.filter();
    const entries = await plugins.fs.readdir(folderPath, { withFileTypes: true });
    const files: string[] = [];
    let bytes = 0;
    for (const entry of entries) {
      const fullPath = plugins.path.join(folderPath, entry.name);
      if (!matchesFilter(entry.name, filter)) {
        continue;
      }
      files.push(fullPath);
      if (entry.isFile()) {
        bytes += (await plugins.fs.stat(fullPath)).size;
      }
    }

    return {
      folder: ensureTrailingSeparator(folderPath),
      filter,
      number_of_files: files.length,
      bytes,
      file_list: files,
    };
  }

  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 filter(): string {
    const metadata = this.configArg.metadata;
    return stringValue(this.configArg.filter) || stringValue(metadata?.filter) || folderDefaultFilter;
  }
}

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

const ensureTrailingSeparator = (pathArg: string): string => pathArg.endsWith(plugins.path.sep) ? pathArg : `${pathArg}${plugins.path.sep}`;

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);
