import { PathLike, appendFileSync, closeSync, existsSync, openSync, readFileSync, statSync, unlinkSync, writeFileSync } from 'fs';
import { TextDocumentItem } from 'vscode-languageserver';
import { LspDocument } from '../document';
import { pathToUri } from './translation';
import { basename, dirname, extname } from 'path';

/**
 * Synchronous file operations.
 */
export class SyncFileHelper {
  static open(filePath: PathLike, flags: string): number {
    const expandedFilePath = this.expandEnvVars(filePath);
    return openSync(expandedFilePath, flags);
  }

  static close(fd: number): void {
    closeSync(fd);
  }

  static read(filePath: PathLike, encoding: BufferEncoding = 'utf8'): string {
    const expandedFilePath = this.expandEnvVars(filePath);
    return readFileSync(expandedFilePath, { encoding });
  }

  static write(filePath: PathLike, data: string, encoding: BufferEncoding = 'utf8'): void {
    const expandedFilePath = this.expandEnvVars(filePath);
    writeFileSync(expandedFilePath, data, { encoding });
  }

  static append(filePath: PathLike, data: string, encoding: BufferEncoding = 'utf8'): void {
    const expandedFilePath = this.expandEnvVars(filePath);
    appendFileSync(expandedFilePath, data, { encoding });
  }

  static expandEnvVars(filePath: PathLike): string {
    let filePathString = filePath.toString();
    // Expand ~ to home directory
    filePathString = filePathString.replace(/^~/, process.env.HOME!);
    // Expand environment variables
    filePathString = filePathString.replace(/\$([a-zA-Z0-9_]+)/g, (_, envVarName) => {
      return process.env[envVarName] || '';
    });
    return filePathString;
  }

  static exists(filePath: PathLike): boolean {
    const expandedFilePath = this.expandEnvVars(filePath);
    return existsSync(expandedFilePath);
  }

  static delete(filePath: PathLike): void {
    unlinkSync(filePath);
  }

  static create(filePath: PathLike) {
    const expandedFilePath = this.expandEnvVars(filePath);
    if (this.isDirectory(expandedFilePath)) {
      return this.getPathTokens(filePath);
    } else if (!this.exists(expandedFilePath)) {
      this.write(expandedFilePath, '');
    }
    return this.getPathTokens(expandedFilePath);
  }

  static getPathTokens(filePath: PathLike) {
    const expandedFilePath = this.expandEnvVars(filePath);
    return {
      path: expandedFilePath,
      filename: basename(expandedFilePath, extname(expandedFilePath)),
      extension: extname(expandedFilePath).substring(1),
      directory: dirname(expandedFilePath),
      exists: this.exists(expandedFilePath),
      uri: pathToUri(expandedFilePath),
    };
  }

  static convertTextToFishFunction(filePath: PathLike, data: string, _encoding: BufferEncoding = 'utf8') {
    const expandedFilePath = this.expandEnvVars(filePath);
    const { filename, path, extension, exists } = this.getPathTokens(expandedFilePath);
    const content = [
      '',
      `function ${filename}`,
      data.split('\n').map(line => '\t' + line).join('\n'),
      'end',
    ].join('\n');

    if (exists) {
      this.append(path, content, 'utf8');
      return this.toLspDocument(path, extension, 1);
    }
    this.write(path, content);
    return this.toLspDocument(path, extension, 1);
  }

  static toTextDocumentItem(filePath: PathLike, languageId: string, version: number): TextDocumentItem {
    const expandedFilePath = this.expandEnvVars(filePath);
    const content = this.read(expandedFilePath);
    const uri = pathToUri(expandedFilePath.toString());
    return TextDocumentItem.create(uri, languageId, version, content);
  }

  static toLspDocument(filePath: PathLike, languageId: string, version: number): LspDocument {
    const expandedFilePath = this.expandEnvVars(filePath);
    let content = this.read(expandedFilePath);

    if (!content) {
      content = '';
    }
    const doc = this.toTextDocumentItem(expandedFilePath, languageId, version);
    return new LspDocument(doc);
  }

  static isDirectory(filePath: PathLike): boolean {
    const expandedFilePath = this.expandEnvVars(filePath);
    try {
      const fileStat = statSync(expandedFilePath);
      return fileStat.isDirectory();
    } catch (_) {
      return false;
    }
  }
}
