// DSL SKIP
import { CallContextEvent, CCE, dedent, getRuntime, PostfixReturn, dslIfaceGuard } from "ts-dsl";
import { FileOperationOptions, FileOperationReturn } from "./fs.model";
import * as fs from 'fs';
import * as YAML from 'yaml';
import * as prependFile from 'prepend-file';
import { promise, TaggedTemplateSelfChain } from 'ts-basis';

export { YAML };

// ====================================================
//  Facade
// ====================================================

class fileModel {
  static exists(file: FileOperationOptions): PostfixReturn<boolean>;
  static exists(file: string): PostfixReturn<boolean>;
  static exists(command) { dslIfaceGuard('lugger:file:exists', __filename); return null; }

  static read(file: FileOperationOptions): PostfixReturn<string>;
  static read(file: FileOperationOptions, encoding: 'buffer'): PostfixReturn<Buffer>;
  static read(file: FileOperationOptions, encoding: BufferEncoding): PostfixReturn<string>;
  static read(file: string): PostfixReturn<string>;
  static read(file: string, encoding: 'buffer'): PostfixReturn<Buffer>;
  static read(file: string, encoding: BufferEncoding): PostfixReturn<string>;
  static read(strArr: TemplateStringsArray, ...args: any[]): TaggedTemplateSelfChain<PostfixReturn<string>>;
  static read(command) { dslIfaceGuard('lugger:file:read', __filename); return null; }

  static readJSON<T extends Object = any>(file: string, encoding?: BufferEncoding): PostfixReturn<T>;
  static readJSON(command) { dslIfaceGuard('lugger:file:readJSON', __filename); return null; }

  static readYAML<T extends Object = any>(file: string, encoding?: BufferEncoding): PostfixReturn<T>;
  static readYAML(command) { dslIfaceGuard('lugger:file:readYAML', __filename); return null; }

  static writeJSON<T extends Object = any>(file: string, object: T, encoding?: BufferEncoding): PostfixReturn<void>;
  static writeJSON(command) { dslIfaceGuard('lugger:file:writeJSON', __filename); return null; }

  static writeYAML<T extends Object = any>(file: string, object: T, encoding?: BufferEncoding): PostfixReturn<void>;
  static writeYAML(command) { dslIfaceGuard('lugger:file:writeYAML', __filename); return null; }

  static write(file: FileOperationOptions, content: Buffer): PostfixReturn<void>;
  static write(file: FileOperationOptions, content: string, encoding?: BufferEncoding): PostfixReturn<void>;
  static write(file: string, content: Buffer): PostfixReturn<void>;
  static write(file: string, content: string, encoding?: BufferEncoding): PostfixReturn<void>;
  static write(strArr: TemplateStringsArray, ...args: any[]): TaggedTemplateSelfChain<PostfixReturn<void>>;
  static write(command) { dslIfaceGuard('lugger:file:write', __filename); return null; }

  static append(file: FileOperationOptions, content: Buffer): PostfixReturn<void>;
  static append(file: FileOperationOptions, content: string, encoding?: BufferEncoding): PostfixReturn<void>;
  static append(file: string, content: Buffer): PostfixReturn<void>;
  static append(file: string, content: string, encoding?: BufferEncoding): PostfixReturn<void>;
  static append(strArr: TemplateStringsArray, ...args: any[]): TaggedTemplateSelfChain<PostfixReturn<void>>;
  static append(command) { dslIfaceGuard('lugger:file:append', __filename); return null; }

  static prepend(file: FileOperationOptions, content: Buffer): PostfixReturn<void>;
  static prepend(file: FileOperationOptions, content: string, encoding?: BufferEncoding): PostfixReturn<void>;
  static prepend(file: string, content: Buffer): PostfixReturn<void>;
  static prepend(file: string, content: string, encoding?: BufferEncoding): PostfixReturn<void>;
  static prepend(strArr: TemplateStringsArray, ...args: any[]): TaggedTemplateSelfChain<PostfixReturn<void>>;
  static prepend(command) { dslIfaceGuard('lugger:file:prepend', __filename); return null; }

  static truncate(file: FileOperationOptions): PostfixReturn<void>;
  static truncate(file: string): PostfixReturn<void>;
  static truncate(command) { dslIfaceGuard('lugger:file:truncate', __filename); return null; }

  static delete(file: FileOperationOptions): PostfixReturn<void>;
  static delete(file: string): PostfixReturn<void>;
  static delete(command) { dslIfaceGuard('lugger:file:delete', __filename); return null; }

  static unlink(file: FileOperationOptions): PostfixReturn<void>;
  static unlink(file: string): PostfixReturn<void>;
  static unlink(command) { dslIfaceGuard('lugger:file:unlink', __filename); return null; }

  static stat(file: FileOperationOptions): PostfixReturn<fs.Stats>;
  static stat(file: string): PostfixReturn<fs.Stats>;
  static stat(command) { dslIfaceGuard('lugger:file:stat', __filename); return null; }
}

export const file = fileModel as (typeof fileModel & ((filename: string) => FileOperationReturn));


// ====================================================
//  Implemenation
// ====================================================

function fileModel__(cce: CallContextEvent, ...args) {
  const { fileOp, encoding, error } = getReadArg(cce, args);
  return getRuntime().scopedExec(
    cce, 'lugger:fs:file', { data: { fileOp } },
    async (resolve, reject, scopeContext) => {
      if (error) { return reject(error); }
      try {
        return resolve(Object.assign({
          isFileTarget: true,
          'on a >> this': (cce: CCE, opName, self, a, b, c) => {
            const content = Buffer.isBuffer(a) ? a: typeof a === 'string' ? Buffer.from(a, self.encoding) : null;
            return promise(async (resolve2, reject2) => {
              fs.appendFile(self.file, content, { encoding }, e => { if (e) { return reject2(e); } return resolve2(void 0); });
            });
          },
          'on this << b': (cce: CCE, opName, self, a, b, c) => {
            const content = Buffer.isBuffer(b) ? b: typeof b === 'string' ? Buffer.from(b, self.encoding) : null;
            return promise(async (resolve2, reject2) => {
              fs.appendFile(self.file, content, { encoding }, e => { if (e) { return reject2(e); } return resolve2(void 0); });
            });
          },
          'on a > this': (cce: CCE, opName, self, a, b, c) => {
            const content = Buffer.isBuffer(a) ? a: typeof a === 'string' ? Buffer.from(a, self.encoding) : null;
            return promise(async (resolve2, reject2) => {
              fs.writeFile(self.file, content, { encoding }, e => { if (e) { return reject2(e); } return resolve2(void 0); });
            });
          },
          'on this < b': (cce: CCE, opName, self, a, b, c) => {
            const content = Buffer.isBuffer(b) ? b: typeof b === 'string' ? Buffer.from(b, self.encoding) : null;
            return promise(async (resolve2, reject2) => {
              fs.writeFile(self.file, content, { encoding }, e => { if (e) { return reject2(e); } return resolve2(void 0); });
            });
          },
        }, fileOp) as any as FileOperationReturn);
      } catch (e) { reject(e); }
    });
}
(fileModel__ as any).exists = (cce: CallContextEvent, ...args) => {
  const { fileOp, encoding, error } = getReadArg(cce, args);
  return getRuntime().scopedExec(
    cce, 'lugger:fs:file.exists', { data: { fileOp } },
    async (resolve, reject, scopeContext) => {
      if (error) { return reject(error); }
      try {
        fs.access(fileOp.file, fs.constants.F_OK, e => {
          return resolve(e ? false : true);
        })
      } catch (e) { reject(e); }
    });
}
(fileModel__ as any).read = (cce: CallContextEvent, ...args) => {
  const { fileOp, encoding, error } = getReadArg(cce, args);
  return getRuntime().scopedExec(
    cce, 'lugger:fs:file.read', { data: { fileOp } },
    async (resolve, reject, scopeContext) => {
      if (error) { return reject(error); }
      try {
        if ((encoding as any) === 'buffer') {
          fs.readFile(fileOp.file, (e, data) => {
            if (e) { return reject(e); }
            return resolve(data);
          });
        } else {
          fs.readFile(fileOp.file, encoding, (e, data) => {
            if (e) { return reject(e); }
            return resolve(data);
          });
        }
      } catch (e) { reject(e); }
    });
}
(fileModel__ as any).readJSON = (cce: CallContextEvent, ...args) => {
  const { fileOp, encoding, error } = getReadArg(cce, args);
  return getRuntime().scopedExec(
    cce, 'lugger:fs:file.readJSON', { data: { fileOp } },
    async (resolve, reject, scopeContext) => {
      if (error) { return reject(error); }
      try {
        fs.readFile(fileOp.file, encoding, (e, data) => {
          if (e) { return reject(e); }
          try { return resolve(JSON.parse(data)); } catch (e2) { return reject(e2); }
        });
      } catch (e) { reject(e); }
    });
}
(fileModel__ as any).readYAML = (cce: CallContextEvent, ...args) => {
  const { fileOp, encoding, error } = getReadArg(cce, args);
  return getRuntime().scopedExec(
    cce, 'lugger:fs:file.readYAML', { data: { fileOp } },
    async (resolve, reject, scopeContext) => {
      if (error) { return reject(error); }
      try {
        fs.readFile(fileOp.file, encoding, (e, data) => {
          if (e) { return reject(e); }
          try { return resolve(YAML.parse(data)); } catch (e2) { return reject(e2); }
        });
      } catch (e) { reject(e); }
    });
}
(fileModel__ as any).write = (cce: CallContextEvent, ...args) => {
  const { fileOp, encoding, content, error } = getWriteArg(cce, args);
  return getRuntime().scopedExec(
    cce, 'lugger:fs:file.write', { data: { fileOp } },
    async (resolve, reject, scopeContext) => {
      if (error) { return reject(error); }
      try {
        fs.writeFile(fileOp.file, content, { encoding }, e => {
          if (e) { return reject(e); }
          return resolve(void 0);
        });
      } catch (e) { reject(e); }
    });
}
(fileModel__ as any).writeJSON = (cce: CallContextEvent, ...args) => {
  const { fileOp, encoding, content, error } = getWriteArg(cce, args, 'json');
  return getRuntime().scopedExec(
    cce, 'lugger:fs:file.writeJSON', { data: { fileOp } },
    async (resolve, reject, scopeContext) => {
      if (error) { return reject(error); }
      try {
        fs.writeFile(fileOp.file, content, { encoding }, e => {
          if (e) { return reject(e); }
          return resolve(void 0);
        });
      } catch (e) { reject(e); }
    });
}
(fileModel__ as any).writeYAML = (cce: CallContextEvent, ...args) => {
  const { fileOp, encoding, content, error } = getWriteArg(cce, args, 'yaml');
  return getRuntime().scopedExec(
    cce, 'lugger:fs:file.writeYAML', { data: { fileOp } },
    async (resolve, reject, scopeContext) => {
      if (error) { return reject(error); }
      try {
        fs.writeFile(fileOp.file, content, { encoding }, e => {
          if (e) { return reject(e); }
          return resolve(void 0);
        });
      } catch (e) { reject(e); }
    });
}
(fileModel__ as any).append = (cce: CallContextEvent, ...args) => {
  const { fileOp, encoding, content, error } = getWriteArg(cce, args);
  return getRuntime().scopedExec(
    cce, 'lugger:fs:file.append', { data: { fileOp } },
    async (resolve, reject, scopeContext) => {
      if (error) { return reject(error); }
      try {
        fs.appendFile(fileOp.file, content, { encoding }, e => {
          if (e) { return reject(e); }
          return resolve(void 0);
        });
      } catch (e) { reject(e); }
    });
}
(fileModel__ as any).prepend = (cce: CallContextEvent, ...args) => {
  const { fileOp, encoding, content, error } = getWriteArg(cce, args);
  return getRuntime().scopedExec(
    cce, 'lugger:fs:file.prepend', { data: { fileOp } },
    async (resolve, reject, scopeContext) => {
      if (error) { return reject(error); }
      try {
        await prependFile.default(fileOp.file, content);
      } catch (e) { reject(e); }
    });
}
(fileModel__ as any).truncate = (cce: CallContextEvent, ...args) => {
  const { fileOp, encoding, error } = getReadArg(cce, args);
  return getRuntime().scopedExec(
    cce, 'lugger:fs:file.prepend', { data: { fileOp } },
    async (resolve, reject, scopeContext) => {
      if (error) { return reject(error); }
      try {
        fs.truncate(fileOp.file, e => {
          if (e) { return reject(e); }
          return resolve(void 0);
        });
      } catch (e) { reject(e); }
    });
}
(fileModel__ as any).delete = (cce: CallContextEvent, ...args) => {
  const { fileOp, encoding, error } = getReadArg(cce, args);
  return getRuntime().scopedExec(
    cce, 'lugger:fs:file.delete', { data: { fileOp } },
    async (resolve, reject, scopeContext) => {
      if (error) { return reject(error); }
      try {
        fs.unlink(fileOp.file, e => {
          if (e) { return reject(e); }
          return resolve(void 0);
        });
      } catch (e) { reject(e); }
    });
}
(fileModel__ as any).unlink = (cce: CallContextEvent, ...args) => {
  const { fileOp, encoding, error } = getReadArg(cce, args);
  return getRuntime().scopedExec(
    cce, 'lugger:fs:file.unlink', { data: { fileOp } },
    async (resolve, reject, scopeContext) => {
      if (error) { return reject(error); }
      try {
        fs.unlink(fileOp.file, e => {
          if (e) { return reject(e); }
          return resolve(void 0);
        });
      } catch (e) { reject(e); }
    });
}
(fileModel__ as any).stat = (cce: CallContextEvent, ...args) => {
  const { fileOp, encoding, error } = getReadArg(cce, args);
  return getRuntime().scopedExec(
    cce, 'lugger:fs:file.stat', { data: { fileOp } },
    async (resolve, reject, scopeContext) => {
      if (error) { return reject(error); }
      try {
        fs.stat(fileOp.file, (e, data) => {
          if (e) { return reject(e); }
          return resolve(data);
        });
      } catch (e) { reject(e); }
    });
}

export const file__ = fileModel__ as any as (typeof file);

function getReadArg(cce: CCE, args: any[]) {
  let error: Error;
  const arg0 = args[0];
  let fileOp: FileOperationOptions = (arg0 as any);
  if (typeof arg0 === 'string') {
    fileOp = cce.fromTaggedTemplate ? { file: arg0 } as FileOperationOptions : { file: arg0 } as FileOperationOptions;
  }
  if (!fileOp.file) {
    error = new Error(`cannot operate on file without file location`);
  }
  let encoding = args[1] as BufferEncoding;
  if (!encoding) { encoding = 'utf8'; fileOp.encoding = encoding;  }
  return { fileOp, encoding, error };
}

function getWriteArg(cce: CCE, args: any[] , contentType: 'default' | 'json' | 'yaml' = 'default') {
  let error: Error;
  const arg0 = args[0];
  let arg1 = args[1];
  let fileOp: FileOperationOptions = (arg0 as any);
  if (typeof arg0 === 'string') {
    fileOp = cce.fromTaggedTemplate ? { file: arg0 } as FileOperationOptions : { file: arg0 } as FileOperationOptions;
  }
  let encoding = args[2] as BufferEncoding;
  if (!encoding) { encoding = 'utf8'; fileOp.encoding = encoding; }
  let content: Buffer = null;
  try {
    if (fileOp?.dedent && typeof arg1 === 'string') {
      arg1 = dedent(arg1);
    }
    if (contentType === 'default') {
      content = Buffer.isBuffer(arg1) ? arg1:
                  typeof arg1 === 'string' ? Buffer.from(arg1, encoding) :
                  null;
    } else if (contentType === 'json') {
      content = Buffer.from(JSON.stringify(arg1, null, 4), encoding);
    } else if (contentType === 'yaml') {
      content = Buffer.from(YAML.stringify(arg1), encoding);
    }
  } catch (e) {
    error = e;
  }
  if (!fileOp.file) {
    error = new Error(`cannot operate on file without file location`);
  }
  return { fileOp, encoding, content, error };
}
