import { createElement } from "../../lib/skeleton/index.js";
import { animate, slideYIn } from "../../lib/animate.js";
import rxjs, { effect } from "../../lib/rx.js";
import { loadCSS } from "../../helpers/loader.js";
import { qs } from "../../lib/dom.js";
import { ApplicationError } from "../../lib/error.js";
import { createLoader } from "../../components/loader.js";
import ctrlError from "../ctrl_error.js";

import { createThing } from "./thing.js";
// import { handleError, getFiles } from "./ctrl_filesystem_state.js";
import { ls } from "./model_files.js";

export default async function(render) {
    const $page = createElement(`
        <div class="component_filesystem container">
            <div class="ifscroll-before"></div>
            <div class="list"></div>
            <div class="ifscroll-after"></div>
            <br>
        </div>
    `);
    render($page);

    // feature: virtual scrolling
    const removeLoader = createLoader($page);
    const path = location.pathname.replace(new RegExp("^/files"), "");
    effect(rxjs.of(path).pipe(
        ls(),
        removeLoader,
        rxjs.mergeMap(({ files, path }) => { // STEP1: setup the list of files
            const FILE_HEIGHT = 160;
            const BLOCK_SIZE = Math.ceil(document.body.clientHeight / FILE_HEIGHT) + 1;
            // const BLOCK_SIZE = 6;
            const COLUMN_PER_ROW = 4;
            const VIRTUAL_SCROLL_MINIMUM_TRIGGER = 50;
            let size = files.length;
            if (size > VIRTUAL_SCROLL_MINIMUM_TRIGGER) {
                size = Math.min(files.length, BLOCK_SIZE * COLUMN_PER_ROW);
            }
            const $list = qs($page, ".list");
            const $fs = document.createDocumentFragment();
            for (let i = 0; i < size; i++) {
                const file = files[i];
                $fs.appendChild(createThing({
                    name: file.name,
                    type: file.type,
                    link: createLink(file, path),
                }));
            }
            animate($list, { time: 200, keyframes: slideYIn(5) });
            $list.appendChild($fs);

            /// //////////////////////////////////////
            // CASE 1: virtual scroll isn't enabled
            if (files.length <= VIRTUAL_SCROLL_MINIMUM_TRIGGER) {
                return rxjs.EMPTY;
            }

            /// //////////////////////////////////////
            // CASE 2: with virtual scroll
            const $listBefore = qs($page, ".ifscroll-before");
            const $listAfter = qs($page, ".ifscroll-after");
            const height = (Math.ceil(files.length / COLUMN_PER_ROW) - BLOCK_SIZE) * FILE_HEIGHT;
            if (height > 33554400) {
                console.log(`maximum CSS height reached, requested height ${height} is too large`);
            }
            const setHeight = (size) => {
                if (size < 0 || size > height) throw new ApplicationError(
                    "INTERNAL ERROR",
                    `assertion on size failed: size[${size}] height[${height}]`
                );
                $listBefore.style.height = `${size}px`;
                $listAfter.style.height = `${height - size}px`;
            };
            setHeight(0);
            // const top = ($node) => $node.getBoundingClientRect().top;
            return rxjs.of({
                files,
                path,
                currentState: 0,
                $list,
                setHeight,
                FILE_HEIGHT,
                BLOCK_SIZE,
                COLUMN_PER_ROW,
                MARGIN: 35, // TODO: top($list) - top($list.closest(".scroll-y"));
            });
        }),
        rxjs.mergeMap(({
            files, path,
            BLOCK_SIZE, COLUMN_PER_ROW, FILE_HEIGHT,
            MARGIN,
            currentState,
            height, setHeight,
            $list,
        }) => rxjs.fromEvent($page.closest(".scroll-y"), "scroll", { passive: true }).pipe(
            rxjs.map((e) => {
                // 0-------------0-----------1-----------2-----------3 ....
                //    [padding]     $block1     $block2     $block3    ....
                const nextState = Math.floor((e.target.scrollTop - MARGIN) / FILE_HEIGHT);
                return Math.max(nextState, 0);
            }),
            rxjs.distinctUntilChanged(),
            rxjs.debounce(() => new rxjs.Observable((observer) => {
                const id = requestAnimationFrame(() => observer.next());
                return () => cancelAnimationFrame(id);
            })),
            rxjs.tap((nextState) => {
                // STEP1: calculate the virtual scroll paramameters
                let diff = nextState - currentState;
                const diffSgn = Math.sign(diff);
                if (Math.abs(diff) > BLOCK_SIZE) { // diff is bound by BLOCK_SIZE
                    // we can't be moving more than what is on the screen
                    diff = diffSgn * BLOCK_SIZE;
                }
                let fileStart = nextState * COLUMN_PER_ROW;
                if (diffSgn > 0) { // => scroll down
                    fileStart += BLOCK_SIZE * COLUMN_PER_ROW;
                    fileStart -= Math.min(diff, BLOCK_SIZE) * COLUMN_PER_ROW;
                }
                let fileEnd = fileStart + diffSgn * diff * COLUMN_PER_ROW;
                if (fileStart >= files.length) { // occur when BLOCK_SIZE is larger than its absolute minimum
                    return;
                }
                else if (fileEnd > files.length) {
                    // occur when files.length isn't a multiple of COLUMN_PER_ROW and
                    // we've scrolled to the bottom of the list already
                    nextState = Math.ceil(files.length / COLUMN_PER_ROW) - BLOCK_SIZE;
                    fileEnd = files.length - 1;
                    for (let i=0; i<COLUMN_PER_ROW; i++) {
                        // add some padding to fileEnd to balance the list to the
                        // nearest COLUMN_PER_ROW
                        fileEnd += 1;
                        if (fileEnd % COLUMN_PER_ROW === 0) {
                            break;
                        }
                    }
                }

                // STEP2: create the new elements
                const $fs = document.createDocumentFragment();
                let n = 0;
                for (let i = fileStart; i < fileEnd; i++) {
                    const file = files[i];
                    if (file === undefined) $fs.appendChild(createThing({
                        type: "hidden",
                    }));
                    else $fs.appendChild(createThing({
                        name: file.name,
                        // name: `file ${i}`,
                        type: file.type,
                        link: createLink(file, path),
                    }));
                    n += 1;
                }

                // STEP3: update the DOM
                if (diffSgn > 0) { // scroll down
                    $list.appendChild($fs);
                    for (let i = 0; i < n; i++) $list.firstChild.remove();
                } else { // scroll up
                    $list.insertBefore($fs, $list.firstChild);
                    for (let i = 0; i < n; i++) $list.lastChild.remove();
                }
                setHeight(nextState * FILE_HEIGHT);
                currentState = nextState;
            }),
        )),
        rxjs.catchError(ctrlError()),
    ));
}

export function init() {
    return Promise.all([
        loadCSS(import.meta.url, "./ctrl_filesystem.css"),
        loadCSS(import.meta.url, "./thing.css"),
    ]);
}

function createLink(file, path) {
    if (file.type === "file") {
        return "/view" + path + file.name;
    }
    return "/files" + path + file.name + "/";
}
