import React, {
  useState,
  useMemo,
  useRef,
  useContext,
  useEffect,
  useCallback,
  createContext,
  createElement,
  FC,
} from 'react';
import { getLocale } from './mock/utils/locale';
// @ts-ignore
import sha1 from 'sha1';
// @ts-ignore
import memoize from 'lodash/memoize';
import jCookie from 'js-cookie';
import { useRequest } from './hooks';
// import { group, repo, prIid, project } from './meta';
import { prService } from './mock/pr.service.js';
import { projectService } from './mock/project.service.js';
import { mapGetSet } from './mock/utils/map-get-set.js';
import { isApiError } from './mock/utils/api-error.js';
import { mockService } from './mock/request/mock';
import { Base64 } from 'js-base64';
import './style.less';



const NoteType = {
  comment: 'Comment',
  problem: 'Problem',
}

const NoteState = {
  opened: 'opened',
  resolved: 'resolved',
  invalid: 'invalid',
}

const CharsetName = {
  gbk: 'GBK',
  utf8: 'UTF-8',
};


function createContainer(useHook, checkNull = false) {
  let Context = createContext(null);
  const Provider = ({ children }) => {
    const value = useHook();
    if (checkNull && !value) return null;
    return <Context.Provider value={value}>{children}</Context.Provider>;
  };
  const useContainer = () => React.useContext(Context);
  return [Provider, useContainer];
}

let ContextProps = createContext(null);
const useGlobal = () => React.useContext(ContextProps);


const [PrProvider, usePr] = createContainer(() => {
  const { params } = useGlobal();
  const { projectsId, owner, mergeId } = params;
  const data = useRequest(async () => {
    const pr = await prService.getPRByIid(projectsId, owner, mergeId);
    return { pr }
  })
  return data;
}, true);


const [SettingProvider, useSetting] = createContainer(() => {
  const { project } = useGlobal();
  const [gbk, setGBK] = useState(() => project.encoding === 'GBK');
  const charsetName = gbk ? CharsetName.gbk : CharsetName.utf8;
  const [locale, setLocal] = useState(() => getLocale());
  const setLocale = React.useCallback(() => {
    jCookie.set('LOCALE', locale === 'en-US' ? 'zh_CN' : 'en_US');
    window.location.reload();
  }, [locale]);


  return {
    gbk,
    setGBK,
    charsetName,
    locale,
    setLocale,
  };
});

// 评论数据
/*  评论数据返回数据分为 note 和 review 参照 /webapi/projects/42422/pull_requests/13055/comments mock接口
 *  note格式 参照 types/note.ts
 *  review 参照 types/review.ts
 *  review 包含 reviewNotes 内有多个note
 *
 *  需传递的数据
 *  noteIdToReplyIdSet  noteIdToNote lineToNoteIdSet  updateFlag
 */
const useCommentPack = (projectId, prId) => {
  const { params } = useGlobal();
  const { projectsId, owner, mergeId } = params;
  // 这个参数理论上不需要传递到ACR组件内
  const lineToNoteIdSet = useMemo(() => new Map(), []);
  // 将note 转换成 noteid => note 形式
  const noteIdToNote = useMemo(() => new Map(), []);
  const noteIdToReviewId = useMemo(() => new Map(), []);
  // 将 review 转化成reviewid => review 形式
  const reviewIdToReview = useMemo(() => new Map(), []);
  // 代码扫描插件使用数据
  const noteIdToReplyIdSet = useMemo(() => new Map(), []);
  // 等待中的review
  const pendingNoteIdSet = useMemo(() => new Set(), []);
  const recordNoteIdSet = useMemo(() => new Set(), []);
  const topLevelCommentNoteIdSet = useMemo(() => new Set(), []);
  // 需要回应的评论
  const commentNoteIdSet = useMemo(() => new Set(), []);
  const openedProblemNoteIdSet = useMemo(() => new Set(), []);
  const [updateFlag, setUpdateFlag] = useState({});

  const fetchingRef = useRef(false);
  const lastFetchAtRef = useRef();

  const pendingReviewRef = useRef();
  const [hasPendingReview, setHasPendingReview] = useState(false); // TODO: 去掉这个state

  function processNote(note, review = null) {
    noteIdToNote.set(note.id, note);
    noteIdToReviewId.set(note.id, review && review.id);
    if (note.type === NoteType.problem && note.state === NoteState.opened) {
      openedProblemNoteIdSet.add(note.id);
    } else {
      openedProblemNoteIdSet.delete(note.id);
    }
    if (!note.discussionId) {
      if (!note.lineCode && note.system) {
        recordNoteIdSet.add(note.id);
      } else {
        topLevelCommentNoteIdSet.add(note.id);
      }
    }
    if (note.discussionId || note.lineCode) {
      commentNoteIdSet.add(note.id);
    }
    if (note.lineCode) {
      const noteIdSet = mapGetSet(
        lineToNoteIdSet,
        note.lineCode,
        () => new Set()
      );
      noteIdSet.add(note.id);
    }
    if (note.discussionId) {
      const replyIdSet = mapGetSet(
        noteIdToReplyIdSet,
        note.discussionId,
        () => new Set()
      );
      replyIdSet.add(note.id);
    }
    if (review && review.pending) {
      pendingNoteIdSet.add(note.id);
    } else {
      pendingNoteIdSet.delete(note.id);
    }
  }

  // 刷新清空数据
  async function doRefresh(force = false, filePath) {
    if (fetchingRef.current) {
      return;
    }
    fetchingRef.current = true;
    // 获取评论内容
    const commentPack = await prService.getCommentPack(projectsId, owner, mergeId, { path: filePath, is_full: true });
    const reviews = await prService.getReviews(projectsId, owner, mergeId);
    // debugger
    for (const review of reviews) {
      review.reviewNotes = commentPack;
    }
    const pack = {
      committedReviews: reviews,
      notes: commentPack,
      pendingReview: reviews[0],
    };
    console.log('pack');
    console.log(pack);
    // debugger
    lastFetchAtRef.current = pack.currentFetchedAt;
    let updated = false;
    if (force) {
      lineToNoteIdSet.clear();
      noteIdToNote.clear();
      noteIdToReviewId.clear();
      reviewIdToReview.clear();
      noteIdToReplyIdSet.clear();
      pendingNoteIdSet.clear();
      recordNoteIdSet.clear();
      topLevelCommentNoteIdSet.clear();
      commentNoteIdSet.clear();
      openedProblemNoteIdSet.clear();
      updated = true;
    }

    for (const note of pack.notes) {
      processNote(note);
      updated = true;
    }
    for (const review of pack.committedReviews) {
      reviewIdToReview.set(review.id, review);
      for (const note of review.reviewNotes) {
        processNote(note, review);
      }
      updated = true;
    }
    if (pack.pendingReview) {
      reviewIdToReview.set(pack.pendingReview.id, pack.pendingReview);
      pendingReviewRef.current = pack.pendingReview;
      setHasPendingReview(true);
      for (const note of pack.pendingReview.reviewNotes) {
        processNote(note, pack.pendingReview);
        updated = true;
      }
    } else {
      setHasPendingReview(false);
    }
    if (updated) {
      setUpdateFlag({});
    }
    fetchingRef.current = false;
  }

  function manualUpdateReview(review) {
    reviewIdToReview.set(review.id, review);
    if (review.pending) {
      pendingReviewRef.current = review;
      setHasPendingReview(!!review);
    }
    setUpdateFlag({});
  }

  function manualAddNote(note, review = null) {
    processNote(note, review);
    setUpdateFlag({});
  }

  function manualRemoveNote(id) {
    noteIdToNote.delete(id);
    setUpdateFlag({});
  }

  function manualUpdateNote(note) {
    const reviewId = noteIdToReviewId.get(note.id);
    const review = reviewId ? reviewIdToReview.get(reviewId) : null;
    processNote(note, review);
    setUpdateFlag({});
  }

  function getRelatedFilePathByNoteId(noteId) {
    const note = noteIdToNote.get(noteId);
    if (!note) return;
    let path = note.path;
    if (note.discussionId) {
      const parent = noteIdToNote.get(note.discussionId);
      path = parent && parent.path;
    }
    return path;
  }

  // 重新编辑
  async function editNote(
    noteId, data
  ) {
    const newNote = await prService.editPRComment(projectsId, owner, mergeId, noteId, data);
    manualUpdateNote(newNote);
    newNote.discussions && newNote.discussions.forEach((note) => manualUpdateNote(note));
  }

  const pathShaToNoteCount = (function () {
    const ret = new Map();

    function handleNote(note) {
      if (!note || !note.lineCode) return;
      const key = note.lineCode.split('_')[0];
      ret.set(key, (ret.get(key) || 0) + 1);
    }

    topLevelCommentNoteIdSet.forEach((noteId) => {
      const note = noteIdToNote.get(noteId);
      handleNote(note);
    });
    return ret;
  })();

  return {
    lineToNoteIdSet,
    noteIdToNote,
    noteIdToReviewId,
    reviewIdToReview,
    noteIdToReplyIdSet,
    pendingNoteIdSet,
    recordNoteIdSet,
    topLevelCommentNoteIdSet,
    openedProblemNoteIdSet,
    hasPendingReview,
    hasOpenedProblem: openedProblemNoteIdSet.size > 0,
    doRefresh,
    manualUpdateReview,
    manualAddNote,
    manualRemoveNote,
    manualUpdateNote,
    pendingReviewRef,
    getRelatedFilePathByNoteId,
    editNote,
    updateFlag,
    pathShaToNoteCount,
    setUpdateFlag,
  };
};

// note 评论相关数据
const [NoteProvider, useNote] = createContainer(() => {

  const { params } = useGlobal();
  const { projectsId, owner, mergeId } = params;
  const { pr: { id: prId } } = usePr();
  const commentPack = useCommentPack(projectsId, mergeId);
  // useEffect(() => {
  //   commentPack.doRefresh();
  // }, []);

  async function addComment(data) {
    if (!commentPack.hasPendingReview) {
      let reviewData = {
        content: data.note,
        status: 'common',
      }
      const review = await prService.createReview(projectsId, owner, mergeId, reviewData);
      commentPack.manualUpdateReview(review);
    }
    const note = await prService.addComment(projectsId, owner, mergeId, data);
    commentPack.manualAddNote(note, commentPack.pendingReviewRef.current);
  }

  async function deleteComment(id) {
    const res = await prService.deleteComment(projectsId, owner, mergeId, id);
    if (res.status == 0) {
      commentPack.manualRemoveNote(id);
    }
  }

  async function commitReview(body) {
    if (!commentPack.hasPendingReview) {
      let reviewData = {
        content: body.note,
        status: 'common',
      }
      await prService.createReview(projectsId, owner, mergeId, reviewData);
    }
    await prService.commitReview(projectsId, owner, mergeId, body);
    await commentPack.doRefresh(true);
  }

  const activateRef = useRef();

  return {
    commentPack,
    ...commentPack,
    addComment,
    deleteComment,
    commitReview,
    prId,
    activateRef,
  };
});

const [ReadMarkProvider, useReadMark] = createContainer(() => {
  const { params } = useGlobal();
  const { projectsId, owner, mergeId } = params;
  const { pr: { id: prId } } = usePr();

  const [flag, updateFlag] = useState({});

  const readMarks = useRequest(
    () => prService.getFileReadMarks(projectsId, owner, mergeId,),
    {
      deps: [projectsId, owner, mergeId, flag],
    }
  );

  const readMarkMap = useMemo(() => {
    const map = new Map();
    if (!readMarks) return map;
    for (const mark of readMarks) {
      mark.markAsRead && map.set(mark.filePathSha, mark);
    }
    return map;
  }, [readMarks]);


  const memoizedSha1 = useMemo(() => memoize(sha1), []);

  async function markFileAsRead(filePath) {
    const data = await prService.markFileAsRead(
      projectsId, owner, mergeId,
      { file_path_sha: Base64.encode(filePath) }
    );
    updateFlag({});
    return data;
  }

  async function markFileAsUnread(filePath) {
    const data = await prService.markFileAsUnread(
      projectsId, owner, mergeId,
      { file_path_sha: Base64.encode(filePath) }
    );
    updateFlag({});
    return data;
  }

  const getFileReadMark = useCallback(
    (filePath) => {
      const filePathSha = memoizedSha1(filePath);
      return readMarkMap.get(filePathSha);
    },
    [readMarkMap]
  );

  const getFileReadStatus = useCallback(
    (filePath) => {
      const readMark = getFileReadMark(filePath);
      return readMark && readMark.markAsRead && !readMark.updatedAfterRead;
    },
    [getFileReadMark]
  );

  return {
    readMarks,
    readMarkMap,
    getFileReadMark,
    getFileReadStatus,
    markFileAsRead,
    markFileAsUnread,
  };
});

const [AcrProvider, useAcr] = createContainer(() => {
  const { params } = useGlobal();
  const { pr } = usePr();
  const { charsetName } = useSetting();
  const { commentPack } = useNote();

  const { projectsId, owner, mergeId } = params;
  const [IDEMode, setIDEMode] = useState(true);
  const toggleViewerType = useCallback(() => {
    setIDEMode((v) => !v);
  }, [setIDEMode]);

  const [stDiff, setStDiff] = useState({});
  const [search, setSearch] = useState(window.location.search);
  const [acrflag, setAcrFlag] = useState();
  const [versions, setVersions] = useState([]);
  const query = useMemo(() => {
    const searchParams = new URLSearchParams(search);
    const ret = {};
    if (searchParams.has('from')) {
      ret.from = parseInt(searchParams.get('from'));
    }
    if (searchParams.has('to')) {
      ret.to = parseInt(searchParams.get('to'));
    }
    return ret;
  }, [search]);

  function updateQuery(data) {
    const searchParam = new URLSearchParams();
    if (data.from) {
      searchParam.set('from', String(data.from));
    }
    if (data.to) {
      searchParam.set('to', String(data.to));
    }
    const search = searchParam.toString();
    window.history.replaceState(
      null,
      '',
      `${window.location.pathname}${search ? '?' : ''}${search}`
    );
    setSearch(search);
  }


  async function getDiffVersions() {
    const data = await prService.getDiffVersions(projectsId, owner, mergeId);
    if (acrflag && data.length == versions.length) {
      setTimeout(() => {
        setAcrFlag({});
      }, 3000);
    }
    setVersions(data);
  }

  useEffect(() => {
    getDiffVersions()
  }, [acrflag])


  const versionMap = useMemo(() => {
    const map = new Map();
    for (const version of versions) {
      map.set(version.id, version);
    }
    return map;
  }, [versions]);
  const latestVersionId = versions[0] && versions[versions.length - 1].id || pr.diff.id;

  const from = query.from;
  const to = query.to || latestVersionId;
  const fromVersion = from ? versionMap.get(from) : null;
  const toVersion = versionMap.get(to);

  console.log('diffpack', from, to, fromVersion, toVersion);
  const ignoreWhiteSpace = false;
  // @ts-ignore
  const diffsPack = useRequest(
    async () => {
      if (!toVersion) return null;
      const isPartial = !!fromVersion || toVersion.id !== latestVersionId;
      const options = {
        ignoreWhiteSpaceChange: ignoreWhiteSpace,
        charsetName,
      };
      let res;
      if (fromVersion) {
        res = await prService.getDiffs(
          projectsId, owner,
          fromVersion.headCommitSha,
          toVersion.headCommitSha,
          options
        );
      } else {
        if (ignoreWhiteSpace) {
          res = await prService.getDiffs(
            projectsId, owner,
            toVersion.baseCommitSha,
            toVersion.headCommitSha,
            options
          );
        } else {
          const diffs = await prService.getDiffOverviews(
            projectsId, owner,
            mergeId,
            toVersion.id
          );
          res = {
            diffs,
            overflow: false,
            addLineNum: toVersion.addLineNum,
            delLineNum: toVersion.delLineNum,
          };
        }
      }
      return {
        ...res,
        fromVersion,
        toVersion,
        isPartial,
      };
    },
    {
      deps: [fromVersion, toVersion, charsetName],
    }
  );

  useEffect(() => {
    commentPack.doRefresh();
  }, []);

  async function getDiffById(diffId) {
    let thisStDiff = await prService.getDiffById(projectsId, owner, mergeId, toVersion.id,
      {
        filepath: diffId,
      });
    delete thisStDiff.sections
    setStDiff(thisStDiff);
    commentPack.doRefresh(false, diffId);
    return thisStDiff;
  }

  async function getFileContent(path, sha, maxSize) {
    try {
      return await projectService.getFileBlob(projectsId, owner, {
        filepath: path,
        ref: sha,
      });
    } catch (e) {
      if (isApiError(e) && e.response.status === 413) {
        return null;
      }
      throw e;
    }
  }

  const annotationPacks = [];
  let countMap = new Map();
  for (const checkSuite of pr.checkSuites) {
    if (checkSuite.checkRuns) {
      for (const checkRun of checkSuite.checkRuns) {
        if (checkRun.annotations) {
          for (const annotation of checkRun.annotations) {
            const pack = {
              annotation,
              checkSuite,
            };
            annotationPacks.push(pack);
            countMap.set(
              annotation.path,
              (countMap.get(annotation.path) || 0) + 1
            );
          }
        }
      }
    }
  }

  return {
    diffsPack,
    versions,
    fromVersion,
    toVersion,
    getDiffById,
    stDiff,
    getFileContent,
    IDEMode,
    toggleViewerType,
    updateQuery,
    setAcrFlag,
    annotationPacks,
  };
});

export const Provider = (props) => {
  const match = props.match;
  let project = {};
  // let user = mockService['/api/v3/user'];
  if (props.projectDetail) {
    let details = props.projectDetail;
    project = {
      ...project,
      ...props.projectDetail,
      pathWithNamespace: details.full_name,
    }
  }
  let user = {};
  if (props.current_user) {
    let current_user = props.current_user;
    user = { ...current_user, avatar_url: current_user.image_url, name: current_user.username, };
  }

  return (
    <ContextProps.Provider value={{
      project, user, params: match.params
    }}>
      {/*  <GlobalProvider> */}
      <PrProvider>
        <SettingProvider>
          <NoteProvider>
            <ReadMarkProvider>
              <AcrProvider>{props.children}</AcrProvider>
            </ReadMarkProvider>
          </NoteProvider>
        </SettingProvider>
      </PrProvider>
      {/*  </GlobalProvider> */}
    </ContextProps.Provider>
  );
};


export { useGlobal, usePr, useNote, useReadMark, useAcr, useSetting };
