import { tokenExpireError } from "Src/authContext";
import TreeSDK from "Utils/TreeSDK";
import MkdSDK from "Utils/MkdSDK";
import {
  REQUEST_FAILED,
  REQUEST_LOADING,
  REQUEST_SUCCESS,
  RequestItems,
  SET_GLOBAL_PROPERTY,
} from "./GlobalConstants";
import { showToast } from "./GlobalContext";

/**
 * @param {any} dispatch - Global Dispatch
 * @param {String | Number | Boolean | Object} data -
 * @param {String} property - any propert name
 */

export const setGLobalProperty = (dispatch, data, property) => {
  // console.log("property >>", property);
  dispatch({
    property,
    type: SET_GLOBAL_PROPERTY,
    payload: data,
  });
};

/**
 * @param {any} dispatch - Global Dispatch
 * @param {boolean} data - true || false
 * @param { String } item - string of the needed action
 */

export function setLoading(dispatch, data, item, where) {
  // console.log("setLoading data >>", data, where);
  dispatch({
    item,
    type: REQUEST_LOADING,
    payload: data,
  });
}

/**
 * @param {any} dispatch - Global Dispatch
 * @param {Array | any} data - any[]
 * @param { String } item - string of the needed action
 */

export const dataSuccess = (dispatch, data, item) => {
  dispatch({
    item,
    type: REQUEST_SUCCESS,
    payload: data,
  });
};
/**
 * @param {any} dispatch - Global Dispatch
 * @param {Array | any} data - any[]
 * @param { String } item - string of the needed action
 */

export const dataFailure = (dispatch, data, item) => {
  dispatch({
    item,
    type: REQUEST_FAILED,
    payload: data,
  });
};

// BASIC SETUP ABOVE

/**
 * @param {any} globalDispatch - Global Dispatch
 * @param {any} authDispatch - Auth Dispatch
 * @param {Number | String} id - project id
 */

export const getProject = async (globalDispatch, authDispatch, id) => {
  const sdk = new MkdSDK();
  setLoading(globalDispatch, true, RequestItems.Project);
  try {
    const result = await sdk.getProject(id);

    if (!result?.error) {
      dataSuccess(globalDispatch, result?.model, RequestItems.Project);
    }
    setLoading(globalDispatch, false, RequestItems.Project);
    // showToast(globalDispatch, "Project Saved", 4000, "info");
  } catch (error) {
    const message = error?.response?.data?.message
      ? error?.response?.data?.message
      : error?.message;
    setLoading(globalDispatch, false, RequestItems.Project);
    dataFailure(globalDispatch, { message, id }, RequestItems.Project);
    showToast(globalDispatch, message, 4000, "error");
    tokenExpireError(authDispatch, message);
  }
};
/**
 * @param {String} url - url
 * @param {Function} cb - cb
 */

export const readImageUrl = (url, cb) => {
  // const sdk = new MkdSDK();

  var xhr = new XMLHttpRequest();

  xhr.open("GET", url, true);

  // xhr.setRequestHeader("x-project", base64Encode);
  // xhr.setRequestHeader(
  //   "Authorization",
  //   "Bearer " + localStorage.getItem("token")
  // );

  xhr.responseType = "arraybuffer";

  xhr.onload = function () {
    if (![401, 402, 403, 404, 500, 502, 501].includes(xhr.status)) {
      var arrayBuffer = xhr.response;
      var blob = new Blob([arrayBuffer], { type: "image/png" });
      // var imageUrl = URL.createObjectURL(blob);
      // Read the blob as Data URL
      var reader = new FileReader();
      reader.onloadend = function () {
        // Callback with Base64 data
        cb({ error: false, data: reader.result, imageBlob: blob });
      };
      reader.readAsDataURL(blob);

      // cb({ error: false, data: imageUrl, imageBlob: blob });
    } else {
      // Handle the error within the xhr.onload event handler
      const errorMessage = `Request failed. Status: ${xhr.status}`;
      console.error(errorMessage);
      cb({ error: true, message: errorMessage });
    }
  };

  xhr.onerror = function () {
    // Handle the error within the xhr.onerror event handler
    const errorMessage = "An error occurred during the request.";
    console.error(errorMessage);
    // cb({ error: true, message: errorMessage });
  };

  // Send the request
  xhr.send();
};

/**
 * @param {any} globalDispatch - Global Dispatch
 * @param {any} authDispatch - Auth Dispatch
 * @param {String} table - table model
 * @param {Number | String} id - id
 * @param {Object} options - options
 * @param {String | String[]} [options.join=null] - join
 * @param {String} [options.method=GET] - method
 * @param {String} [options.state=RequestItems?.viewModel] - any string to represent the context field, default is viewModel
 * @param {Boolean} [options.allowToast=true] - default true | set false if you don't want to see the toast automatically
 * @param {Boolean} [options.isPublic=false] - default false | set true if you want to skip the token expiration check
 */

export const getSingleModel = async (
  globalDispatch,
  authDispatch,
  table,
  id,
  options = {
    method: "GET",
    join: null,
    allowToast: true,
    state: RequestItems?.viewModel,
    isPublic: false,
  }
) => {
  const sdk = new MkdSDK();
  // console.log("table >>", table, options);
  setLoading(globalDispatch, true, options?.state);
  try {
    sdk.setTable(table.trim());
    const result = await sdk.callRestAPI(
      {
        id: Number(id),
        ...{ ...(options?.join ? { join: options?.join } : null) },
      },
      options?.method
    );

    if (!result?.error) {
      dataSuccess(globalDispatch, { data: result?.model }, options?.state);
    }
    setLoading(globalDispatch, false, options?.state);
    // showToast(globalDispatch, "Project Saved", 4000, "info");
    return {
      error: false,
      data: result?.model,
      message: result?.message ?? "Success",
    };
  } catch (error) {
    const message = error?.response?.data?.message
      ? error?.response?.data?.message
      : error?.message;
    setLoading(globalDispatch, false, null);
    dataFailure(globalDispatch, { message, id }, options?.state);
    if (options?.allowToast) {
      showToast(globalDispatch, message, 4000, "error");
    }
    if (!options?.isPublic) {
      tokenExpireError(authDispatch, message);
    }
    return { error: true, message };
  }
};

/**
 * @param {any} globalDispatch - Global Dispatch
 * @param {any} authDispatch - Auth Dispatch
 * @param {String} table - table model
 * @param {Array[Number] | Array[String]} filter - list of filters
 * @param { String } join - join table
 * @param {Boolean} allowToast - default true | set fakse if you don't want to see the toast automatically
 */

export const getMany = async (
  globalDispatch,
  authDispatch,
  table,
  filter = [],
  join = null,
  allowToast = true
) => {
  const tdk = new TreeSDK();
  setLoading(globalDispatch, true, RequestItems?.listModel);
  try {
    // const filters = ids.map((id) => computeFilter("id", "in", id));
    const result = await tdk.getList(table, {
      ...{
        ...(join ? { join: join } : null),
        ...(filter && filter?.length ? { filter: filter } : null),
      },
    });

    if (!result?.error) {
      dataSuccess(
        globalDispatch,
        { data: result?.list },
        RequestItems?.listModel
      );
    }
    setLoading(globalDispatch, false, RequestItems?.listModel);
    return { error: false, data: result?.list };
    // showToast(globalDispatch, "Project Saved", 4000, "info");
  } catch (error) {
    const message = error?.response?.data?.message
      ? error?.response?.data?.message
      : error?.message;
    setLoading(globalDispatch, false, RequestItems?.listModel);
    // dataFailure(globalDispatch, { message, id }, RequestItems?.listModel);
    if (allowToast) {
      showToast(globalDispatch, message, 4000, "error");
    }
    tokenExpireError(authDispatch, message);
    return { error: true, message };
  }
};

/**
 * @param {any} globalDispatch - Global Dispatch
 * @param {any} authDispatch - Auth Dispatch
 * @param {String} table - table model
 * @param {Array[Number] | Array[String]} ids - list of ids
 * @param { String } join - join table
 * @param {Boolean} allowToast - default true | set fakse if you don't want to see the toast automatically
 */

export const getManyByIds = async (
  globalDispatch,
  authDispatch,
  table,
  ids,
  join = null,
  allowToast = true
) => {
  // console.log("ids >>", ids);
  // console.log("join >>", join);
  const tdk = new TreeSDK();
  setLoading(globalDispatch, true, RequestItems?.listModel);
  try {
    const result = await tdk.getList(table, {
      ...{
        ...(join ? { join: join } : null),
        filter: [`id,in,${ids.join(",")}`],
      },
    });

    if (!result?.error) {
      dataSuccess(
        globalDispatch,
        { data: result?.list },
        RequestItems?.listModel
      );
    }
    setLoading(globalDispatch, false, RequestItems?.listModel);
    return { error: result?.error, data: result?.list };
    // showToast(globalDispatch, "Project Saved", 4000, "info");
  } catch (error) {
    const message = error?.response?.data?.message
      ? error?.response?.data?.message
      : error?.message;
    setLoading(globalDispatch, false, RequestItems?.listModel);
    dataFailure(globalDispatch, { message, ids }, RequestItems?.listModel);
    if (allowToast) {
      showToast(globalDispatch, message, 4000, "error");
    }
    tokenExpireError(authDispatch, message);
    return { error: true, message };
  }
};

/**
 * @param {any} globalDispatch - Global Dispatch
 * @param {any} authDispatch - Auth Dispatch
 * @param {String} table - table model
 * @param { Object } payload - data to create
 * @param {Boolean} allowToast - default true | set fakse if you don't want to see the toast automatically
 */

export const createRequest = async (
  globalDispatch,
  authDispatch,
  table,
  payload,
  allowToast = true
) => {
  const tdk = new TreeSDK();
  setLoading(globalDispatch, true, RequestItems?.createModel);
  try {
    const result = await tdk.create(table, payload);

    if (!result?.error) {
      dataSuccess(
        globalDispatch,
        { message: result?.message, data: result?.data },
        RequestItems?.createModel
      );
      setLoading(globalDispatch, false, RequestItems?.createModel);
      if (allowToast) {
        showToast(globalDispatch, result?.message, 4000, "success");
      }
      return { error: false, data: result?.data, message: result?.message };
    } else {
      setLoading(globalDispatch, false, RequestItems?.createModel);
      if (allowToast) {
        showToast(globalDispatch, result?.message, 4000, "error");
      }
      return { error: true, message: result?.message };
    }
  } catch (error) {
    const message = error?.response?.data?.message
      ? error?.response?.data?.message
      : error?.message;
    setLoading(globalDispatch, false, RequestItems?.createModel);
    dataFailure(globalDispatch, { message }, RequestItems?.createModel);
    if (allowToast) {
      showToast(globalDispatch, message, 4000, "error");
    }
    tokenExpireError(authDispatch, message);
    return { error: true, message };
  }
};

/**
 * @param {any} globalDispatch - Global Dispatch
 * @param {any} authDispatch - Auth Dispatch
 * @param {String} table - table model
 * @param {String | Number} id - item id
 * @param { Object } payload - data to update
 * @param {Boolean} allowToast - default true | set fakse if you don't want to see the toast automatically
 */

export const updateRequest = async (
  globalDispatch,
  authDispatch,
  table,
  id,
  payload,
  allowToast = true
) => {
  const tdk = new TreeSDK();
  setLoading(globalDispatch, true, RequestItems?.updateModel);
  try {
    const result = await tdk.update(table, id, payload);

    if (!result?.error) {
      // dataSuccess(globalDispatch, { message: result?.message }, RequestItems?.updateModel);
      setLoading(globalDispatch, false, RequestItems?.updateModel);
      if (allowToast) {
        showToast(globalDispatch, result?.message, 4000, "success");
      }
      return { error: false };
    } else {
      setLoading(globalDispatch, false, RequestItems?.updateModel);
      if (allowToast) {
        showToast(globalDispatch, result?.message, 4000, "error");
      }
      return { error: true };
    }
  } catch (error) {
    const message = error?.response?.data?.message
      ? error?.response?.data?.message
      : error?.message;
    setLoading(globalDispatch, false, RequestItems?.updateModel);
    // dataFailure(globalDispatch, { message }, RequestItems?.updateModel);
    if (allowToast) {
      showToast(globalDispatch, message, 4000, "error");
    }
    tokenExpireError(authDispatch, message);
    return { error: true };
  }
};

/**
 * @param {any} globalDispatch - Global Dispatch
 * @param {any} authDispatch - Auth Dispatch
 * @param {String} table - table model
 * * @param {String | Number} id - item id
 * @param { Object } payload - data to delete
 * @param {Boolean} allowToast - default true | set fakse if you don't want to see the toast automatically
 */

export const deleteRequest = async (
  globalDispatch,
  authDispatch,
  table,
  id,
  payload,
  allowToast = true
) => {
  const tdk = new TreeSDK();
  setLoading(globalDispatch, true, RequestItems?.deleteModel);
  try {
    const result = await tdk.delete(table, id, payload);

    if (!result?.error) {
      dataSuccess(
        globalDispatch,
        { message: result?.message },
        RequestItems?.deleteModel
      );
      setLoading(globalDispatch, false, RequestItems?.deleteModel);
      if (allowToast) {
        showToast(globalDispatch, result?.message, 4000, "success");
      }
      return { error: false, data: result?.data };
    } else {
      setLoading(globalDispatch, false, RequestItems?.deleteModel);
      if (allowToast) {
        showToast(globalDispatch, result?.message, 4000, "error");
      }
      return { error: true };
    }
  } catch (error) {
    const message = error?.response?.data?.message
      ? error?.response?.data?.message
      : error?.message;
    setLoading(globalDispatch, false, RequestItems?.deleteModel);
    dataFailure(globalDispatch, { message }, RequestItems?.deleteModel);
    if (allowToast) {
      showToast(globalDispatch, message, 4000, "error");
    }
    tokenExpireError(authDispatch, message);
    return { error: true };
  }
};

/**
 * @param {any} globalDispatch - Global Dispatch
 * @param {any} authDispatch - Auth Dispatch
 * @param {String} table - table model
 * @param { Object } options - options are filter, join, size, direction and order query
 * @param { string } state - an optional string use as the data retrieval state in place of table
 * @param { Array } [options.filter=null] filter: []
 * @param { String } [options.join=null] join: "table1,table2,table3" - joins another table
 * @param { Number } [options.size=10] size number of items per page
 * @param { String } [options.order=id] order by any field | default is the id field
 * @param { String } [options.direction=desc] direction, desc or asc | desc is the default
 */

export const getList = async (
  globalDispatch,
  authDispatch,
  table,
  options = {},
  state
) => {
  // console.log("options >>", options);
  const tdk = new TreeSDK();
  setLoading(globalDispatch, true, state ?? table);
  try {
    const result = await tdk.getList(table, options);

    if (!result?.error) {
      dataSuccess(
        globalDispatch,
        { message: result?.message, data: result?.list },
        state ?? table
      );
      setLoading(globalDispatch, false, state ?? table);
      // showToast(globalDispatch, result?.message, 4000, "success");
      return { error: false, data: result?.list };
    } else {
      setLoading(globalDispatch, false, state ?? table);
      // showToast(globalDispatch, result?.message, 4000, "error");
      return result;
    }
  } catch (error) {
    const message = error?.response?.data?.message
      ? error?.response?.data?.message
      : error?.message;
    setLoading(globalDispatch, false, state ?? table);
    // dataFailure(globalDispatch, { message }, table);
    // showToast(globalDispatch, message, 4000, "error");
    tokenExpireError(authDispatch, message);
    return { error: true, message };
  }
};

/**
 * @typedef {Object} CustomRequestOptions
 * @property {String} endpoint - the api endpoint
 * @property { any } payload - Request Data
 * @property { String } method - Request Method GET | POST | PATCH | DELETE | PUT - default -> GET
 */

/**
 * @param {any} globalDispatch - Global Dispatch
 * @param {any} authDispatch - Auth Dispatch
 * @param { CustomRequestOptions } options - payload data
 * @param { String } state - a field in the global content to access the loading state
 * @param {Boolean} allowToast - default true | set fakse if you don't want to see the toast automatically
 */

export const customRequest = async (
  globalDispatch,
  authDispatch,
  options = { endpoint: "", paylod: null, method: "GET" },
  state = RequestItems.customRequest,
  allowToast = true,
  signal = null
) => {
  if (!options.endpoint) {
    showToast(
      globalDispatch,
      "options.endpoint is a required field",
      4000,
      "error"
    );
    return { error: true };
  }
  const sdk = new MkdSDK();
  setLoading(globalDispatch, true, state);
  try {
    const result = await sdk.customRequest(
      options?.endpoint,
      options?.method,
      options?.payload,
      signal
    );

    if (!result?.error) {
      dataSuccess(
        globalDispatch,
        { message: result?.message, data: result?.data, error: false },
        state
      );
      setLoading(globalDispatch, false, state);
      if (allowToast) {
        showToast(
          globalDispatch,
          result?.message ?? "Success",
          4000,
          "success"
        );
      }
      return {
        ...result,
        error: false,
        data: result?.data || result?.model || result?.list,
        message: result?.message,
      };
    } else {
      setLoading(globalDispatch, false, state);
      if (allowToast) {
        showToast(
          globalDispatch,
          result?.message ?? "An Error Occurred",
          4000,
          "error"
        );
      }
      return {
        ...result,
        error: true,
        validation: result?.validation,
        message: result?.message,
      };
    }
  } catch (error) {
    const message = error?.response?.data?.message
      ? error?.response?.data?.message
      : error?.message;
    setLoading(globalDispatch, false, state);
    dataFailure(globalDispatch, { message, error: true }, state);
    if (allowToast) {
      showToast(globalDispatch, message, 4000, "error");
    }
    console.log("error?.response >>", error?.response);
    tokenExpireError(authDispatch, message);
    return { ...error?.response?.data, error: true, message };
  }
};

function computeFilter(field, operator, value) {
  return `${field},${operator},${value}`;
}
