import { router } from "@inertiajs/react";
import { SaveButton } from "../../components/buttons/SaveButton";
import { Button } from "primereact/button";
import { Dialog } from "primereact/dialog";
import React, { Dispatch, SetStateAction, useState } from "react";
import { Nullable } from "primereact/ts-helpers";

export type EntryValue =
  | string
  | number
  | boolean
  | Date
  | Date[]
  | Nullable<(Date | null)[]>;

export interface EntryField {
  value: EntryValue;
  label?: string | null;
  hint?: string | null;
  error?: string | null;
  required?: boolean;
}

export type DataModel<Fields extends string> = {
  [Key in Fields]: EntryField;
};

// eslint-disable-next-line react-refresh/only-export-components
export enum SaveScheme {
  SINGLE,
  DOUBLE,
}

interface PreparedModel {
  [key: string]: EntryValue;
}

class FormHelper<
  Fields extends keyof Model,
  Model extends Record<Fields, EntryField>,
> {
  // @ts-expect-error Set in constructor via private method (setDialogState)
  public model: Model;
  // @ts-expect-error Set in constructor via private method (setDialogState)
  public setModel: (value: SetStateAction<Model>) => void;
  public emptyModel: Model;
  public formError: boolean = false;
  public preparedModel: PreparedModel = {};
  public saveUri: string;
  public saveType: SaveScheme;

  // @ts-expect-error Set in constructor via private method (setDialogState)
  public saveCloseBtnClicked: boolean;
  // @ts-expect-error Set in constructor via private method (setDialogState)
  public setSaveCloseBtnClicked: Dispatch<SetStateAction<boolean>>;
  // @ts-expect-error Set in constructor via private method (setDialogState)
  public saveNextBtnClicked: boolean;
  // @ts-expect-error Set in constructor via private method (setDialogState)
  public setSaveNextBtnClicked: Dispatch<SetStateAction<boolean>>;

  // @ts-expect-error Set in constructor via private method (setDialogState)
  public dialogVisible: boolean;
  // @ts-expect-error Set in constructor via private method (setDialogState)
  public setDialogVisible: Dispatch<SetStateAction<boolean>>;

  public extraValues: PreparedModel;

  protected postSaveHook: () => void;

  constructor(
    emptyModel: Model,
    saveUri: string,
    saveType: SaveScheme,
    extraValues = {},
    postSaveHook = () => {},
  ) {
    this.emptyModel = emptyModel;
    this.saveUri = saveUri;
    this.saveType = saveType;
    this.extraValues = extraValues;
    this.postSaveHook = postSaveHook;

    this.setModelStates();
    this.setButtonStates();
    this.setDialogState();
  }

  private setModelStates = () => {
    const [model, setModel] = useState<Model>(this.emptyModel);
    this.model = model;
    this.setModel = setModel;
  };

  private setButtonStates = () => {
    const [saveNextBtnClicked, setSaveNextBtnClicked] = useState(false);
    const [saveCloseBtnClicked, setSaveCloseBtnClicked] = useState(false);

    this.saveCloseBtnClicked = saveCloseBtnClicked;
    this.setSaveCloseBtnClicked = setSaveCloseBtnClicked;
    this.saveNextBtnClicked = saveNextBtnClicked;
    this.setSaveNextBtnClicked = setSaveNextBtnClicked;
  };

  private setDialogState = () => {
    const [dialogVisible, setDialogVisible] = useState(false);
    this.dialogVisible = dialogVisible;
    this.setDialogVisible = setDialogVisible;
  };

  static blankField = (
    label: string | null = null,
    hint: string | null = null,
    required: boolean = false,
  ): EntryField => ({
    value: "",
    label: label,
    hint: hint,
    error: null,
    required: required,
  });

  public setEmptyModel(fields: Model) {
    this.emptyModel = fields;
    this.setModel(this.emptyModel);
  }

  public updateValue = (
    newValue: EntryValue,
    field: Fields,
    error: string = "This cannot be blank",
    validate: boolean = true,
  ) => {
    const model: Model = { ...this.model };
    model[field].value = newValue;
    if (validate) {
      if (newValue === "" || newValue === null) {
        model[field].error = error;
      } else {
        model[field].error = null;
      }
    }
    this.setModel(model);
  };

  public setExtraValues = (values: PreparedModel) => {
    this.extraValues = values;
  };

  public prepareSave = (): boolean => {
    interface Error {
      id: keyof Model;
      error: string;
    }
    const model: PreparedModel = {};
    const errors: Error[] = [];
    for (const entry in this.model) {
      const id = entry;
      if (this.model[id].required && this.model[id].value === "") {
        errors.push({ id: id, error: "This cannot be blank" });
      }
      model[id] = this.model[id].value;
    }
    if (errors.length > 0) {
      errors.forEach((error) =>
        this.updateValue(
          this.model[error.id].value,
          error.id as Fields,
          error.error,
        ),
      );
      return false;
    }

    for (const entry in this.extraValues) {
      model[entry] = this.extraValues[entry];
    }

    this.preparedModel = model;
    return true;
  };

  public resetForm = () => {
    this.setModel(this.emptyModel);
  };

  public resetButtons = () => {
    this.setSaveCloseBtnClicked(false);
    if (this.setSaveNextBtnClicked) {
      this.setSaveNextBtnClicked(false);
    }
  };

  public saveTask = (close: boolean) => {
    if (this.saveType === SaveScheme.SINGLE) {
      this.setSaveCloseBtnClicked(true);
    } else {
      if (close) {
        this.setSaveCloseBtnClicked(true);
      } else {
        this.setSaveNextBtnClicked && this.setSaveNextBtnClicked(true);
      }
    }
    this.saveForm(close);
  };

  public saveForm = (close: boolean = false) => {
    if (!this.prepareSave()) {
      this.resetButtons();
      return;
    }
    router.post(this.saveUri, this.preparedModel, {
      onError: (errorMessage) => {
        for (const key in errorMessage) {
          const message = errorMessage[key];
          if (this.model[key as Fields]) {
            this.model[key as Fields].error = message;
          }
        }
        this.resetButtons();
      },
      onSuccess: () => {
        this.resetButtons();
        this.resetForm();
        if (close) {
          this.setDialogVisible(false);
        }
        this.postSaveHook();
      },
    });
  };

  public onCloseDialog = () => {
    this.resetButtons();
    this.resetForm();
    this.setDialogVisible(false);
  };

  public saveButtonContent = () => {
    if (this.saveType === SaveScheme.SINGLE) {
      return (
        <SaveButton
          onClick={() => this.saveTask(true)}
          buttonClicked={this.saveCloseBtnClicked}
        />
      );
    }

    return (
      <>
        <SaveButton
          onClick={() => this.saveTask(true)}
          buttonClicked={this.saveCloseBtnClicked}
          normalText="Save & Close"
        />
        <SaveButton
          onClick={() => this.saveTask(false)}
          buttonClicked={this.saveNextBtnClicked as boolean}
          normalText="Save & Next"
        />
      </>
    );
  };

  // To Render the form, use the following
  // ./FormButtonDialog
}

export default FormHelper;

interface FormProps<
  Fields extends string,
  Model extends Record<Fields, EntryField>,
> {
  header: string;
  children: React.ReactNode;
  Form: FormHelper<Fields, Model>;
  maximizable?: boolean;
  buttonLabel?: string;
  onCloseHook?: () => void;
  dialogPosition?:
    | "center"
    | "top"
    | "bottom"
    | "left"
    | "right"
    | "top-left"
    | "top-right"
    | "bottom-left"
    | "bottom-right"
    | undefined;
}

export const FormButtonDialog = <
  Fields extends string,
  Model extends Record<Fields, EntryField>,
>({
  header,
  children,
  Form,
  maximizable = true,
  buttonLabel = "Create",
  onCloseHook = undefined,
  dialogPosition = undefined,
}: FormProps<Fields, Model>) => {
  return (
    <>
      <Button
        className="mb-2"
        label={buttonLabel}
        icon="fa-solid fa-plus"
        onClick={() => Form.setDialogVisible(true)}
      />
      <Dialog
        header={header}
        footer={Form.saveButtonContent()}
        visible={Form.dialogVisible}
        style={{ minWidth: "75vw" }}
        maximizable={maximizable}
        position={dialogPosition}
        onHide={() => {
          if (!Form.dialogVisible) return;
          Form.onCloseDialog();
          if (onCloseHook !== undefined) {
            onCloseHook();
          }
        }}
      >
        {children}
      </Dialog>
    </>
  );
};

export const FormDialog = <
  Fields extends string,
  Model extends Record<Fields, EntryField>,
>({
  header,
  children,
  Form,
  maximizable = true,
}: FormProps<Fields, Model>) => {
  return (
    <>
      <Dialog
        header={header}
        footer={Form.saveButtonContent()}
        visible={Form.dialogVisible}
        style={{ minWidth: "75vw" }}
        maximizable={maximizable}
        onHide={() => {
          if (!Form.dialogVisible) return;
          Form.onCloseDialog();
        }}
      >
        {children}
      </Dialog>
    </>
  );
};
