import {createContext, Dispatch} from 'react';
import {produce} from 'immer';
import {FileSystem} from './types';

type LoadingInfo = {
  status: "notstarted"|"inprogress"|"success"|"error";
  errorMessage: string;
  error: any;
};

export type BundleType = {
  id: number;
  type: 'plugins'|'navigators'|'ios-jsbundle'|'android-jsbundle';
  cdnlink: string;
  tag: string;
};

function narrowBundleType(raw: any): raw is BundleType  {
  return (typeof raw.id === "number") &&
    (["plugins", "navigators", "ios-jsbundle", "android-jsbundle"].includes(raw.type)) &&
    (typeof raw.cdnlink === "string") &&
    (typeof raw.tag === "string");
}

function narrowBundleTypeArray(raw: any): raw is BundleType[] {
  let result = true;
  for (let i = 0; i < raw.length; ++i) {
    const bundle = raw[i];
    result = result && narrowBundleType(bundle);
    if (!result) break;
  }
  return result;
}

type AppDataPayload = {
  manifest: {
    name: string;
    uuid: string;
    codeArtefacts: BundleType[];
    gitRepo: string|null;
    iosBundleId: number|null;
    navigatorsBundleId: number|null;
    pluginsBundleId: number|null;
    androidBundleId: number|null;
    forks: Array<{
      id: number; 
      title: string; 
      publishedCommitId: number;
    }>;
  };
  appIntegrations: Array<{
    id: number;
    integrationCode: string;
    packageLocation: null|string;
    title: string;
  }>;
  apptileConfig: {
    apptileServer: string;
    appconfigServer: string;
  };
  bundles: BundleType[];
};

type Loadable<T> = {
  loadingInfo: LoadingInfo;
  payload: T;
}

export type StoreState = {
  appData: Loadable<AppDataPayload>;
  workspaceData: Loadable<Array<FileSystem.WorkspaceData>>;
  devicesData: Loadable<{ios: FileSystem.MobileDevice[], android: FileSystem.MobileDevice[]}>;
  currentAppRepoOffset: number;
};

export const initialState: StoreState = {
  appData: {
    loadingInfo: {
      status: "notstarted",
      errorMessage: "",
      error: null
    },
    payload: {
      manifest: {
        name: "",
        uuid: "",
        gitRepo: null,
        iosBundleId: null,
        navigatorsBundleId: null,
        pluginsBundleId: null,
        androidBundleId: null,
        codeArtefacts: [],
        forks: []
      },
      appIntegrations: [],
      apptileConfig: {
        apptileServer: "",
        appconfigServer: ""
      },
      bundles: []
    }
  }, 
  workspaceData: {
    loadingInfo: {
      status: "notstarted",
      errorMessage: "",
      error: null
    },
    payload: []
  },
  devicesData: {
    loadingInfo: {
      status: "notstarted",
      errorMessage: "",
      error: null
    },
    payload: {ios: [], android: []}
  },
  currentAppRepoOffset: -1
};

export function narrowAppDataPayload(rawData: any): rawData is AppDataPayload {
  try {
    // validate manifest
    let result = true;
    result = result && (typeof rawData.manifest.name === "string");
    result = result && (typeof rawData.manifest.uuid === "string");
    result = result && ((typeof rawData.manifest.gitRepo === "string") || 
                        (rawData.manifest.gitRepo === null));
    result = result && ((typeof rawData.manifest.iosBundleId === "number") ||
                        (rawData.manifest.iosBundleId === null));
    result = result && ((typeof rawData.manifest.navigatorsBundleId === "number") ||
                        (rawData.manifest.iosBundleId === null));
    result = result && ((typeof rawData.manifest.pluginsBundleId === "number") ||
                        (rawData.manifest.pluginsBundleId === null));
    result = result && narrowBundleTypeArray(rawData.manifest.codeArtefacts);

    for (let i = 0; i < rawData?.manifest?.forks.length; ++i) {
      const fork = rawData?.manifest?.forks[i];
      result = result && 
        (typeof fork.id === "number") && 
        (typeof fork.title === "string") &&
        (typeof fork.publishedCommitId === "number");
      if (!result) {
        break;
      }
    }

    // validate appIntegrations
    for (let i = 0; i < rawData.appIntegrations.length; ++i) {
      const integration = rawData.appIntegrations[i];
      result = result && 
        (typeof integration.id === "string") &&
        (typeof integration.integrationCode === "string") &&
        (typeof integration.packageLocation === "string") && 
        (typeof integration.title === "string");
    }

    // validate apptileConfig
    result = result && 
      (typeof rawData.apptileConfig.apptileServer === "string") &&
      (typeof rawData.apptileConfig.appconfigServer === "string");

    result = result && narrowBundleTypeArray(rawData.bundles);

    return result;
  } catch (err) {
    console.error("Failed to validate AppDataPayload", err);
    return false;
  }
}

type StoreAction = {
  type: 'APPDATA_CLEAR';
} 
| 
{
  type: 'APPDATA_SET_LOADING';
} 
| 
{
  type: 'APPDATA_SET_ERROR';
  payload: {err: Error; message: string;};
} 
|
{
  type: 'APPDATA_SET_LOADED';
  payload: AppDataPayload;
} 
|
{
  type: 'WORKSPACE_CLEAR';
}
| 
{
  type: 'WORKSPACE_SET_LOADING';
} 
| 
{
  type: 'WORKSPACE_SET_ERROR';
  payload: {err: Error; message: string;};
} 
|
{
  type: 'WORKSPACE_SET_LOADED';
  payload: FileSystem.WorkspaceData[];
}
|
{
  type: 'DEVICES_CLEAR';
}
| 
{
  type: 'DEVICES_SET_LOADING';
} 
| 
{
  type: 'DEVICES_SET_ERROR';
  payload: {err: Error; message: string;};
} 
|
{
  type: 'DEVICES_SET_LOADED';
  payload: StoreState['devicesData']['payload'];
}

;

function getCurrentAppOffset(state: StoreState): number {
  if ((state.appData.loadingInfo.status === 'success') &&
      (state.workspaceData.loadingInfo.status === 'success')) {
    let result = -1;
    outer: for (let wrkIndex = 0; wrkIndex < state.workspaceData.payload.length; ++wrkIndex) {
      const workspace = state.workspaceData.payload[wrkIndex];
      for (let i = 0; i < workspace.repos.length; ++i) {
        const repo = workspace.repos[i];
        if ((repo.appId === state.appData.payload.manifest.uuid) && repo.isOpen) {
          result = i;
          break outer;
        }
      }
    }
    return result;
  } else {
    return -1;
  }
}

export const fsReducer = produce((state: StoreState, action: StoreAction) => {
  switch (action.type) {
  case 'APPDATA_CLEAR':
    state.appData.loadingInfo.status = 'notstarted';
    state.currentAppRepoOffset = -1;
  break;
  case 'APPDATA_SET_LOADING':
    state.appData.loadingInfo.status = 'inprogress';
    state.currentAppRepoOffset = -1;
  break;
  case 'APPDATA_SET_ERROR':
    state.appData.loadingInfo.status = 'error';
    state.appData.loadingInfo.errorMessage = action.payload.message;
    state.appData.loadingInfo.error = action.payload.err;
    state.currentAppRepoOffset = -1;
  break;
  case 'APPDATA_SET_LOADED':
    state.appData.loadingInfo.status = 'success';
    state.appData.payload = action.payload;
    state.currentAppRepoOffset = getCurrentAppOffset(state);
  break;
  case 'WORKSPACE_CLEAR':
    state.workspaceData.loadingInfo.status = 'notstarted';
    state.currentAppRepoOffset = -1;
  break;
  case 'WORKSPACE_SET_LOADING':
    state.workspaceData.loadingInfo.status = 'inprogress';
    state.currentAppRepoOffset = -1;
  break;
  case 'WORKSPACE_SET_ERROR': 
    state.workspaceData.loadingInfo.status = 'error';
    state.workspaceData.loadingInfo.errorMessage = action.payload.message;
    state.workspaceData.loadingInfo.error = action.payload.err;
    state.currentAppRepoOffset = -1;
  break;
  case 'WORKSPACE_SET_LOADED':
    state.workspaceData.loadingInfo.status = 'success';
    state.workspaceData.payload = action.payload;
    state.currentAppRepoOffset = getCurrentAppOffset(state);
  break;
  case 'DEVICES_CLEAR':
    state.devicesData.loadingInfo.status = 'notstarted';
  break;
  case 'DEVICES_SET_LOADING':
    state.devicesData.loadingInfo.status = 'inprogress';
  break;
  case 'DEVICES_SET_ERROR': 
    state.devicesData.loadingInfo.status = 'error';
    state.devicesData.loadingInfo.errorMessage = action.payload.message;
    state.devicesData.loadingInfo.error = action.payload.err;
  break;
  case 'DEVICES_SET_LOADED':
    state.devicesData.loadingInfo.status = 'success';
    state.devicesData.payload = action.payload;
  break;
  default:
    throw new Error("Unhandled action");
  }
});

export let StoreContext = createContext<StoreState | null>(null);
type DispatchFcn = Dispatch<Parameters<typeof fsReducer>[1]>;
export let StoreDispatchContext  = createContext<
    DispatchFcn |((...args: any[]) => void)
  >((...args: any[]) => console.log("Cannot process: ", args));

export function fetchDevicesData(dispatch: DispatchFcn, clearBeforeLoad: boolean, setError: boolean) {
  debugger
  if (clearBeforeLoad) {
    dispatch({
      type: 'DEVICES_SET_LOADING'
    }); 
  }

  fetch(`http://localhost:3100/plugin-server/home/devices`)
    .then(res => res.json())
    .then(data => {
      if (data.ios && 
          data.android && 
          FileSystem.narrowMobileDeviceArray(data.ios) &&
          FileSystem.narrowMobileDeviceArray(data.android)
         ) {
        dispatch({
          type: 'DEVICES_SET_LOADED',
          payload: data
        });
      } else {
        console.error("Failed to validate workspace data");
        if (setError) {
          dispatch({
            type: 'DEVICES_SET_ERROR',
            payload: {
              message: 'Could not validate devices data',
              err: new Error('Could not validate devices data')
            }
          })
        }
      }
    })
    .catch(err => {
      console.error("Failed to fetch devices: ", err);
      if (setError) {
        dispatch({
          type: 'DEVICES_SET_ERROR',
          payload: {
            message: 'Invalid workspace app data ' + err.message,
            err: err
          }
        });
      }
    });

}

export function fetchWorkspaceData(dispatch: DispatchFcn, clearBeforeLoad: boolean, setError: boolean) {
  if (clearBeforeLoad) {
    dispatch({
      type: 'WORKSPACE_SET_LOADING'
    }); 
  }

  fetch(`http://localhost:3100/plugin-server/cli/workspaces/list`)
    .then(res => res.json())
    .then(data => {
      if (FileSystem.narrowWorkspaceDataArray(data)) {
        dispatch({
          type: 'WORKSPACE_SET_LOADED',
          payload: data
        });
      } else {
        console.error("Failed to validate workspace data");
        if (setError) {
          dispatch({
            type: 'WORKSPACE_SET_ERROR',
            payload: {
              message: 'Could not validate workspace data',
              err: new Error('Could not validate workspace data')
            }
          })
        }
      }
    })
    .catch(err => {
      console.error("Failed to fetch app details: ", err);
      if (setError) {
        dispatch({
          type: 'WORKSPACE_SET_ERROR',
          payload: {
            message: 'Invalid workspace app data ' + err.message,
            err: err
          }
        });
      }
    });
}


