/* eslint-disable @typescript-eslint/no-require-imports */
import Konva from "konva";
import {Vector2d} from "konva/lib/types";
import * as OT from '@mescius/js-collaboration-ot-client';
import {Presence} from "@mescius/js-collaboration-presence-client";
import {useEffect, useRef, useState} from "react";
import {konva_type} from "../../ot-types";
import {IDoc} from "../../request/doc";
import {IUser, IUserEx} from "../../request/user";
import {randomColor} from "./util";
import {connect} from "../../request/req";
import {CustomHeader, PageType} from '../Header';

const { uuid } = require('../../util/util');

OT.TypesManager.register(konva_type);

interface IPresence {
    point: { x: number, y: number };
    user: IUserEx;
}

export default function SharedKonva(props: { doc: IDoc, user: IUser, readonly: boolean }) {
    const ref = useRef<HTMLDivElement>(null);
    const [p, setP] = useState<IPresence[]>([]);
    const { doc, user } = props;

    useEffect(() => {
        const div = ref.current as HTMLDivElement;
        const conn = connect(doc.id);
        const sharedDoc = new OT.SharedDoc<IData, IBatchOp>(conn);
        const presence = new Presence<IPresence>(conn);

        const stage = new Konva.Stage({
            container: div,
            width: div.clientWidth,
            height: div.clientHeight,
        });

        const color = randomColor();
        bind(sharedDoc, stage, color, props.readonly);
        const destroyPresence = bindPresence(presence, div, { ...user, color }, setP);

        return () => {
            sharedDoc.destroy();
            conn.close();
            destroyPresence();
            stage.destroy();
        }
    }, [doc.id, user])

    return (
        <CustomHeader type={PageType.Open} doc={doc}>
            <div style={{ position: 'relative', height: "100%", width: "100%", background: 'white', border: '1px solid #ccc' }}>
                <div ref={ref} style={{ height: "100%" }}></div>
                {p.filter((t) => t.point.x >= 0 && t.point.y >= 0).map((p) => <Cursor p={p}></Cursor>)}
            </div>
        </CustomHeader>
    )
}

const Cursor = (props: { p: IPresence }) => {
    const { p } = props;
    return <div style={{
        position: 'absolute',
        left: p.point.x + 'px',
        top: p.point.y + 'px',
        borderRadius: "50%",
        backgroundColor: p.user.color,
        width: "20px",
        height: "20px",
        pointerEvents: "none",
        zIndex: 999
    }}></div>
}



export interface IOp {
    type: 'addLine' | 'addPoint',
    data: any;
}

export type IBatchOp = IOp[];
export type IData = IOp[];

export async function bind(doc: OT.SharedDoc<IData, IBatchOp>, stage: Konva.Stage, color: string, readonly: boolean) {
    const layer = new Konva.Layer();
    stage.add(layer);

    let line: Konva.Line | null;
    let lastPointerPosition: Vector2d;

    doc.on('op', (op, source) => {
        if (source === doc.connection.id) return;
        applyOps(layer, op);
    });

    await doc.subscribe();
    if (doc.data) {
        fromJSON(stage, doc.data);
    }

    stage.on('mousedown touchstart', function (e) {
        if (readonly || (e.type === 'mousedown' && e.evt.button !== 0)) {
            return;
        }

        lastPointerPosition = stage.getPointerPosition() as Vector2d;

        line = new Konva.Line({
            id: uuid(),
            stroke: color,
            strokeWidth: 5,
            points: [lastPointerPosition.x, lastPointerPosition.y]
        });
        layer.add(line);

        doc.submitOp([{ type: 'addLine', data: JSON.parse(line.toJSON()) }], { source: doc.connection.id });
    });

    stage.on('mouseup touchend', function () {
        line = null;
    });

    stage.on('mousemove touchmove', function () {
        if (!line) return;
        const { x, y } = stage.getPointerPosition() as Vector2d;

        // 绘制线段
        line.points(line.points().concat([x, y]));

        doc.submitOp([{ type: 'addPoint', data: { lineId: line.attrs.id, x, y } }], { source: doc.connection.id });

        lastPointerPosition = { x, y };
        layer.batchDraw();
    });
}

export function bindPresence(presence: Presence<IPresence>, div: HTMLDivElement, user: IUserEx, setPresences: (ps: IPresence[]) => void) {
    const updatePresences = () => {
        console.log('self', presence.id, 'others:', presence.otherStates);
        const ps: IPresence[] = [];
        for (const id in presence.otherStates) {
            ps.push(presence.otherStates[id]);
        }
        setPresences(ps);
    };

    presence.subscribe().then(() => {
        updatePresences();
    });
    presence.on('add', updatePresences);
    presence.on('update', updatePresences);
    presence.on('remove', updatePresences);

    presence.submitLocalState({ point: { x: -1, y: -1 }, user });

    const submitLocalState = (e: PointerEvent) => {
        let x = -1, y = -1;
        if (e.type === 'pointermove') {
            x = e.offsetX;
            y = e.offsetY;
        }
        presence.submitLocalState({ point: { x, y }, user });
    };
    div.addEventListener('pointermove', submitLocalState);
    div.addEventListener('pointerleave', submitLocalState);

    return () => {
        div.removeEventListener('pointermove', submitLocalState);
        div.removeEventListener('pointerleave', submitLocalState);
    };
}

export function fromJSON(stage: Konva.Stage, data: IData) {
    const layer = stage.getLayers()[0];

    data.forEach((op) => applyOp(layer, op));
}

export function applyOp(layer: Konva.Layer, op: IOp) {
    if (op.type === 'addLine') {
        layer.add(new Konva.Line(op.data));
    } else if (op.type === 'addPoint') {
        const { lineId, x, y } = op.data;
        const line = layer.findOne((c: any) => c.attrs.id === lineId) as Konva.Line;
        line.points(line.points().concat([x, y]));
    }
    layer.batchDraw();
}

export function applyOps(layer: Konva.Layer, ops: IOp[]) {
    ops.forEach(op => applyOp(layer, op));
}
