import * as React from "react";
import * as ReactDOM from "react-dom";
import { List, Map, Set } from "immutable";
import FileSize from "filesize";

import { RiFolder2Fill } from "@react-icons/all-files/ri/RiFolder2Fill";
import { RiHomeSmileFill } from "@react-icons/all-files/ri/RiHomeSmileFill";
import { RiFile2Fill } from "@react-icons/all-files/ri/RiFile2Fill";

import { alertMsg, confirmMsg } from "../common/env";
import { updater } from "./state_updater";
import { ICoreState, MsgProps, UIProps } from "./core_state";
import { LoginProps } from "./pane_login";
import { MetadataResp, roleVisitor, roleAdmin } from "../client";
import { Flexbox } from "./layout/flexbox";
import { Up } from "../worker/upload_mgr";
import { UploadEntry, UploadState } from "../worker/interface";

export interface Item {
  name: string;
  size: number;
  modTime: string;
  isDir: boolean;
  selected: boolean;
  sha1: string;
}

export interface FilesProps {
  dirPath: List<string>;
  isSharing: boolean;
  items: List<MetadataResp>;
}

export interface Props {
  filesInfo: FilesProps;
  msg: MsgProps;
  login: LoginProps;
  ui: UIProps;
  update?: (updater: (prevState: ICoreState) => ICoreState) => void;
}

export function getItemPath(dirPath: string, itemName: string): string {
  return dirPath.endsWith("/")
    ? `${dirPath}${itemName}`
    : `${dirPath}/${itemName}`;
}

export interface State {
  newFolderName: string;
  selectedSrc: string;
  selectedItems: Map<string, boolean>;
  showDetail: Set<string>;
  uploadFiles: string;
}

export class FilesPanel extends React.Component<Props, State, {}> {
  private uploadInput: Element | Text;
  private assignInput: (input: Element) => void;
  private onClickUpload: () => void;

  constructor(p: Props) {
    super(p);
    this.state = {
      newFolderName: "",
      selectedSrc: "",
      selectedItems: Map<string, boolean>(),
      showDetail: Set<string>(),
      uploadFiles: "",
    };

    Up().setStatusCb(this.updateProgress);
    this.uploadInput = undefined;
    this.assignInput = (input) => {
      this.uploadInput = ReactDOM.findDOMNode(input);
    };
    this.onClickUpload = () => {
      const uploadInput = this.uploadInput as HTMLButtonElement;
      uploadInput.click();
    };
  }

  updateProgress = async (
    infos: Map<string, UploadEntry>,
    refresh: boolean
  ) => {
    updater().setUploadings(infos);
    let errCount = 0;
    infos.valueSeq().forEach((entry: UploadEntry) => {
      errCount += entry.state === UploadState.Error ? 1 : 0;
    });

    if (infos.size === 0 || infos.size === errCount) {
      // refresh used space
      updater()
        .self()
        .then(() => {
          this.props.update(updater().updateLogin);
        });
    }

    if (refresh) {
      updater()
        .setItems(this.props.filesInfo.dirPath)
        .then(() => {
          this.props.update(updater().updateFilesInfo);
        });
    } else {
      this.props.update(updater().updateFilesInfo);
    }
  };

  addUploads = (event: React.ChangeEvent<HTMLInputElement>) => {
    if (event.target.files.length > 1000) {
      alertMsg(this.props.msg.pkg.get("err.tooManyUploads"));
      return;
    }

    let fileList = List<File>();
    for (let i = 0; i < event.target.files.length; i++) {
      fileList = fileList.push(event.target.files[i]);
    }
    updater().addUploads(fileList);
    this.props.update(updater().updateFilesInfo);
    this.props.update(updater().updateSharingsInfo);
  };

  onNewFolderNameChange = (ev: React.ChangeEvent<HTMLInputElement>) => {
    this.setState({ newFolderName: ev.target.value });
  };

  onMkDir = () => {
    if (this.state.newFolderName === "") {
      alertMsg(this.props.msg.pkg.get("browser.folder.add.fail"));
      return;
    }

    const dirPath = getItemPath(
      this.props.filesInfo.dirPath.join("/"),
      this.state.newFolderName
    );
    updater()
      .mkDir(dirPath)
      .then(() => {
        this.setState({ newFolderName: "" });
        return updater().setItems(this.props.filesInfo.dirPath);
      })
      .then(() => {
        this.props.update(updater().updateFilesInfo);
        this.props.update(updater().updateSharingsInfo);
      });
  };

  delete = () => {
    // TODO: selected should be cleaned after change the cwd
    if (this.props.filesInfo.dirPath.join("/") !== this.state.selectedSrc) {
      alertMsg(this.props.msg.pkg.get("browser.del.fail"));
      this.setState({
        selectedSrc: this.props.filesInfo.dirPath.join("/"),
        selectedItems: Map<string, boolean>(),
      });
      return;
    } else {
      const filesToDel = this.state.selectedItems.keySeq().join(", ");
      if (
        !confirmMsg(
          `${this.props.msg.pkg.get("op.confirm")} [${
            this.state.selectedItems.size
          }]: ${filesToDel}`
        )
      ) {
        return;
      }
    }

    updater()
      .delete(
        this.props.filesInfo.dirPath,
        this.props.filesInfo.items,
        this.state.selectedItems
      )
      .then(() => {
        return updater().self();
      })
      .then(() => {
        this.props.update(updater().updateFilesInfo);
        this.props.update(updater().updateSharingsInfo);
        this.props.update(updater().updateLogin);
        this.setState({
          selectedSrc: "",
          selectedItems: Map<string, boolean>(),
        });
      });
  };

  moveHere = () => {
    const oldDir = this.state.selectedSrc;
    const newDir = this.props.filesInfo.dirPath.join("/");
    if (oldDir === newDir) {
      alertMsg(this.props.msg.pkg.get("browser.move.fail"));
      return;
    }

    updater()
      .moveHere(
        this.state.selectedSrc,
        this.props.filesInfo.dirPath.join("/"),
        this.state.selectedItems
      )
      .then(() => {
        this.props.update(updater().updateFilesInfo);
        this.props.update(updater().updateSharingsInfo);
        this.setState({
          selectedSrc: "",
          selectedItems: Map<string, boolean>(),
        });
      });
  };

  gotoChild = async (childDirName: string) => {
    return this.chdir(this.props.filesInfo.dirPath.push(childDirName));
  };

  chdir = async (dirPath: List<string>) => {
    if (dirPath === this.props.filesInfo.dirPath) {
      return;
    } else if (this.props.login.userRole !== roleAdmin && dirPath.size <= 1) {
      alertMsg(this.props.msg.pkg.get("unauthed"));
      return;
    }

    return updater()
      .setItems(dirPath)
      .then(() => {
        return updater().listSharings();
      })
      .then(() => {
        return updater().isSharing(dirPath.join("/"));
      })
      .then(() => {
        this.props.update(updater().updateFilesInfo);
        this.props.update(updater().updateSharingsInfo);
      });
  };

  select = (itemName: string) => {
    const selectedItems = this.state.selectedItems.has(itemName)
      ? this.state.selectedItems.delete(itemName)
      : this.state.selectedItems.set(itemName, true);

    this.setState({
      selectedSrc: this.props.filesInfo.dirPath.join("/"),
      selectedItems: selectedItems,
    });
  };

  selectAll = () => {
    let newSelected = Map<string, boolean>();
    const someSelected = this.state.selectedItems.size === 0 ? true : false;
    if (someSelected) {
      this.props.filesInfo.items.forEach((item) => {
        newSelected = newSelected.set(item.name, true);
      });
    } else {
      this.props.filesInfo.items.forEach((item) => {
        newSelected = newSelected.delete(item.name);
      });
    }

    this.setState({
      selectedSrc: this.props.filesInfo.dirPath.join("/"),
      selectedItems: newSelected,
    });
  };

  toggleDetail = (name: string) => {
    const showDetail = this.state.showDetail.has(name)
      ? this.state.showDetail.delete(name)
      : this.state.showDetail.add(name);
    this.setState({ showDetail });
  };

  generateHash = async (filePath: string): Promise<boolean> => {
    alertMsg(this.props.msg.pkg.get("refresh-hint"));
    return updater().generateHash(filePath);
  };

  addSharing = async () => {
    return updater()
      .addSharing()
      .then((ok) => {
        if (!ok) {
          alertMsg(this.props.msg.pkg.get("browser.share.add.fail"));
        } else {
          updater().setSharing(true);
        }
      })
      .then(() => {
        this.props.update(updater().updateFilesInfo);
        this.props.update(updater().updateSharingsInfo);
      });
  };

  deleteSharing = async (dirPath: string) => {
    return updater()
      .deleteSharing(dirPath)
      .then((ok) => {
        if (!ok) {
          alertMsg(this.props.msg.pkg.get("browser.share.del.fail"));
        } else {
          updater().setSharing(false);
        }
      })
      .then(() => {
        this.props.update(updater().updateFilesInfo);
        this.props.update(updater().updateSharingsInfo);
      });
  };

  render() {
    const showOp = this.props.login.userRole === roleVisitor ? "hidden" : "";
    const breadcrumb = this.props.filesInfo.dirPath.map(
      (pathPart: string, key: number) => {
        return (
          <button
            key={pathPart}
            onClick={() =>
              this.chdir(this.props.filesInfo.dirPath.slice(0, key + 1))
            }
            className="item"
          >
            {pathPart}
          </button>
        );
      }
    );

    const nameWidthClass = `item-name item-name-${
      this.props.ui.isVertical ? "vertical" : "horizontal"
    } pointer`;

    const ops = (
      <div id="upload-op">
        <div className="float">
          <input
            type="text"
            onChange={this.onNewFolderNameChange}
            value={this.state.newFolderName}
            placeholder={this.props.msg.pkg.get("browser.folder.name")}
            className="float"
          />
          <button onClick={this.onMkDir} className="float">
            {this.props.msg.pkg.get("browser.folder.add")}
          </button>
        </div>

        <div className="float">
          <button onClick={this.onClickUpload}>
            {this.props.msg.pkg.get("browser.upload")}
          </button>
          <input
            type="file"
            onChange={this.addUploads}
            multiple={true}
            value={this.state.uploadFiles}
            ref={this.assignInput}
            className="hidden"
          />
        </div>
      </div>
    );

    const sortedItems = this.props.filesInfo.items.sort(
      (item1: MetadataResp, item2: MetadataResp) => {
        if (item1.isDir && !item2.isDir) {
          return -1;
        } else if (!item1.isDir && item2.isDir) {
          return 1;
        }
        return 0;
      }
    );

    const itemList = sortedItems.map((item: MetadataResp) => {
      const isSelected = this.state.selectedItems.has(item.name);
      const dirPath = this.props.filesInfo.dirPath.join("/");
      const itemPath = dirPath.endsWith("/")
        ? `${dirPath}${item.name}`
        : `${dirPath}/${item.name}`;

      return item.isDir ? (
        <Flexbox
          key={item.name}
          children={List([
            <span className="padding-m">
              <Flexbox
                children={List([
                  <RiFolder2Fill
                    size="3rem"
                    className="yellow0-font margin-r-m"
                  />,

                  <span className={`${nameWidthClass}`}>
                    <span
                      className="title-m"
                      onClick={() => this.gotoChild(item.name)}
                    >
                      {item.name}
                    </span>
                    <div className="desc-m grey0-font">
                      <span>
                        {item.modTime.slice(0, item.modTime.indexOf("T"))}
                      </span>
                    </div>
                  </span>,
                ])}
                childrenStyles={List([
                  { flex: "0 0 auto" },
                  { flex: "0 0 auto" },
                ])}
              />
            </span>,

            <span className={`item-op padding-m ${showOp}`}>
              <button
                onClick={() => this.select(item.name)}
                className={`${
                  isSelected ? "cyan0-bg white-font" : "grey2-bg grey3-font"
                }`}
                style={{ width: "8rem", display: "inline-block" }}
              >
                {isSelected
                  ? this.props.msg.pkg.get("browser.deselect")
                  : this.props.msg.pkg.get("browser.select")}
              </button>
            </span>,
          ])}
          childrenStyles={List([
            { flex: "0 0 auto", width: "60%" },
            { flex: "0 0 auto", justifyContent: "flex-end", width: "40%" },
          ])}
        />
      ) : (
        <div key={item.name}>
          <Flexbox
            key={item.name}
            children={List([
              <Flexbox
                children={List([
                  <RiFile2Fill
                    size="3rem"
                    className="cyan0-font margin-l-m margin-r-m"
                  />,

                  <span className={`${nameWidthClass}`}>
                    <a
                      className="title-m"
                      href={`/v1/fs/files?fp=${itemPath}`}
                      target="_blank"
                    >
                      {item.name}
                    </a>
                    <div className="desc-m grey0-font">
                      <span>
                        {item.modTime.slice(0, item.modTime.indexOf("T"))}
                      </span>
                      &nbsp;/&nbsp;
                      <span>{FileSize(item.size, { round: 0 })}</span>
                    </div>
                  </span>,
                ])}
                childrenStyles={List([
                  { flex: "0 0 auto" },
                  { flex: "0 0 auto" },
                ])}
              />,

              <span className={`item-op padding-m ${showOp}`}>
                <button
                  onClick={() => this.toggleDetail(item.name)}
                  style={{ width: "8rem" }}
                  className="float-input"
                >
                  {this.props.msg.pkg.get("detail")}
                </button>

                <button
                  type="button"
                  onClick={() => this.select(item.name)}
                  className={`float-input ${
                    isSelected ? "cyan0-bg white-font " : "grey2-bg grey3-font "
                  }`}
                  style={{ width: "8rem" }}
                >
                  {isSelected
                    ? this.props.msg.pkg.get("browser.deselect")
                    : this.props.msg.pkg.get("browser.select")}
                </button>
              </span>,
            ])}
            childrenStyles={List([
              { flex: "0 0 auto", width: "60%" },
              { flex: "0 0 auto", justifyContent: "flex-end", width: "40%" },
            ])}
          />

          <div
            className={`${
              this.state.showDetail.has(item.name) ? "" : "hidden"
            }`}
          >
            <Flexbox
              children={List([
                <span>
                  <b>SHA1:</b>
                  {` ${item.sha1}`}
                </span>,
                <button
                  onClick={() => this.generateHash(itemPath)}
                  className="black-bg white-font margin-l-m"
                  style={{ display: "inline-block" }}
                >
                  {this.props.msg.pkg.get("refresh")}
                </button>,
              ])}
              className={`grey2-bg grey3-font detail margin-r-m`}
              childrenStyles={List([{}, { justifyContent: "flex-end" }])}
            />
          </div>
        </div>
      );
    });

    const usedSpace = FileSize(parseInt(this.props.login.usedSpace, 10), {
      round: 0,
    });
    const spaceLimit = FileSize(
      parseInt(this.props.login.quota.spaceLimit, 10),
      {
        round: 0,
      }
    );

    const itemListPane = (
      <div id="item-list">
        <div className={`container ${showOp}`}>{ops}</div>

        <div className="container">
          <div id="browser-op" className={`${showOp}`}>
            <Flexbox
              children={List([
                <span>
                  {this.props.filesInfo.isSharing ? (
                    <button
                      type="button"
                      onClick={() => {
                        this.deleteSharing(
                          this.props.filesInfo.dirPath.join("/")
                        );
                      }}
                      className="red-btn"
                    >
                      {this.props.msg.pkg.get("browser.share.del")}
                    </button>
                  ) : (
                    <button
                      type="button"
                      onClick={this.addSharing}
                      className="cyan-btn"
                    >
                      {this.props.msg.pkg.get("browser.share.add")}
                    </button>
                  )}
                </span>,

                <span>
                  {this.state.selectedItems.size > 0 ? (
                    <span>
                      <button
                        type="button"
                        onClick={() => this.delete()}
                        className="red-btn"
                      >
                        {this.props.msg.pkg.get("browser.delete")}
                      </button>

                      <button type="button" onClick={() => this.moveHere()}>
                        {this.props.msg.pkg.get("browser.paste")}
                      </button>
                    </span>
                  ) : null}
                </span>,

                <span>
                  <span
                    id="space-used"
                    className="desc-m grey0-font"
                  >{`${this.props.msg.pkg.get(
                    "browser.used"
                  )} ${usedSpace} / ${spaceLimit}`}</span>
                </span>,
              ])}
              childrenStyles={List([
                { flex: "0 0 auto" },
                { flex: "0 0 auto" },
                { justifyContent: "flex-end" },
              ])}
            />
          </div>

          <Flexbox
            children={List([
              <span id="breadcrumb">
                <Flexbox
                  children={List([
                    <RiHomeSmileFill size="3rem" id="icon-home" />,
                    <Flexbox children={breadcrumb} />,
                  ])}
                  childrenStyles={List([
                    { flex: "0 0 auto" },
                    { flex: "0 0 auto" },
                  ])}
                />
              </span>,

              <span className={`${showOp}`}>
                <button onClick={() => this.selectAll()} className="select-btn">
                  {this.props.msg.pkg.get("browser.selectAll")}
                </button>
              </span>,
            ])}
            childrenStyles={List([{}, { justifyContent: "flex-end" }])}
          />

          {itemList}
        </div>
      </div>
    );

    return <div id="browser">{itemListPane}</div>;
  }
}
