import {
  ParseReturnType,
  ZodIssueCode,
  ZodParsedType,
  ZodType,
  INVALID,
  ZodTypeDef,
  addIssueToContext,
  ParseInput,
} from "zod";
import { ErrMessage, errToObj } from "./common-helpers";

const zodFileKind = "ZodFile";

declare type ZodFileCheck =
  | {
      kind: "binary";
      message?: string;
    }
  | {
      kind: "base64";
      message?: string;
    };

export interface ZodFileDef extends ZodTypeDef {
  checks: ZodFileCheck[];
  typeName: typeof zodFileKind;
}

const base64Regex =
  /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;

export class ZodFile extends ZodType<string, ZodFileDef> {
  _parse(input: ParseInput): ParseReturnType<string> {
    const { status, ctx } = this._processInputParams(input);
    if (ctx.parsedType !== ZodParsedType.string) {
      addIssueToContext(ctx, {
        code: ZodIssueCode.invalid_type,
        expected: ZodParsedType.string,
        received: ctx.parsedType,
      });
      return INVALID;
    }

    for (const check of this._def.checks) {
      if (check.kind === "base64") {
        if (!base64Regex.test(ctx.data)) {
          addIssueToContext(ctx, {
            code: ZodIssueCode.custom,
            message: check.message,
          });
          status.dirty();
        }
      }
    }

    return { status: status.value, value: ctx.data };
  }

  binary = (message?: ErrMessage) =>
    new ZodFile({
      ...this._def,
      checks: [...this._def.checks, { kind: "binary", ...errToObj(message) }],
    });

  base64 = (message?: ErrMessage) =>
    new ZodFile({
      ...this._def,
      checks: [...this._def.checks, { kind: "base64", ...errToObj(message) }],
    });

  get isBinary() {
    return !!this._def.checks.find((check) => check.kind === "binary");
  }

  get isBase64() {
    return !!this._def.checks.find((check) => check.kind === "base64");
  }

  static create = () =>
    new ZodFile({
      checks: [],
      typeName: zodFileKind,
    });
}