import router from '@/router';
import { useWebSocket } from '@vueuse/core';
import { ElMessage } from 'element-plus';
import type { ValueOf } from 'element-plus/es/components/table/src/table-column/defaults.mjs';
import { ensureArray } from './array';
import type { MaybeArray } from './types';

export interface SimplePart<N extends string> {
  name: N;
}
type Payload = Record<string, unknown>;
export interface PayloadPart<N extends string, P extends Payload = {}> extends SimplePart<N> {
  payload: P;
}
type BaseResp = PayloadPart<string, Record<string, unknown>>;
export type Relations<TRequestNames extends string, TRespNames extends string> = Partial<
  Record<TRequestNames, MaybeArray<TRespNames, true>>
>;
export interface UseGameSocketOptions<
  TRequest extends SimplePart<string>,
  TResp extends BaseResp,
  TRelations extends Relations<TRequest['name'], TResp['name']>,
> {
  url: string;
  relations: TRelations;
  succeed: {
    [P in TResp['name']]?: (payload: Extract<TResp, { name: P }>['payload']) => void;
  };
  error?: {
    [P in TRequest['name']]?: (reason: string) => boolean | void;
  };
  before?: {
    // 只有拥有依赖关系的请求才可以有before
    [P in keyof TRelations]?: (error: boolean) => void;
  };
  finally?: {
    // 只有拥有依赖关系的请求才可以有finally
    [P in keyof TRelations]?: (error: boolean) => void;
  };
  disconnected?(): void;
}
export function useGameSocket<TRequest extends SimplePart<string>, TResp extends BaseResp>() {
  return <TRelations extends Relations<TRequest['name'], TResp['name']>>(
    options: UseGameSocketOptions<TRequest, TResp, TRelations>,
  ) => {
    const { relations } = options;
    interface ErrorResp extends PayloadPart<'Error', { reason: string }> {
      originalPacketName: TRequest['name'];
    }
    const isErrorResp = (obj: BaseResp): obj is ErrorResp => obj.name === 'Error';
    /** resp->request */
    const reversedRelations: Record<string, Set<string> | undefined> = {};
    for (const [k, values] of Object.entries<ValueOf<TRelations>>(relations as any)) {
      if (!values) continue;
      for (const v of ensureArray(values)) {
        (reversedRelations[v] ??= new Set()).add(k);
      }
    }
    const relationMap = new Map<keyof TRelations, [TRequest, Set<string>]>();
    const ws = useWebSocket<string>(options.url, {
      onMessage(_, { data }) {
        const resp: TResp | ErrorResp = JSON.parse(data);
        console.log(resp);
        const name = resp.name as TResp['name'];
        let reqName: keyof TRelations | undefined;
        let error = false;
        try {
          if (isErrorResp(resp)) {
            const { originalPacketName, payload } = resp;
            error = true;
            if (relations[originalPacketName]) {
              reqName = originalPacketName;
              options.before?.[reqName]?.(true);
              relationMap.delete(originalPacketName);
            }
            const errorHandler = options.error?.[originalPacketName];
            if (errorHandler) {
              const res = errorHandler(payload.reason);
              if (res) return;
            }
            ElMessage.error(payload.reason);
            return;
          }
          const set = reversedRelations[name];
          if (set) {
            for (const k of set) {
              const v = relationMap.get(k);
              if (!v) continue;
              const [, set] = v;
              set.delete(name);
              if (set.size) continue;
              relationMap.delete(k);
              reqName = k;
              options.before?.[reqName]?.(false);
              break;
            }
          }
          options.succeed[name]?.(resp.payload as any);
        } finally {
          if (reqName && relations[reqName]) {
            options.finally?.[reqName]?.(error);
          }
        }
      },
      onDisconnected(_, ev) {
        if (
          // 正常断开
          ev.code === 1000 ||
          // 连接未完成就断开
          ev.code === 1006
        )
          return;
        ElMessage.error(`连接已断开：${ev.reason}`);
        router.push('/');
      },
    });
    const send = (data: TRequest) => {
      const name = data.name as TRequest['name'];
      if (relations[name]) {
        relationMap.set(name, [data, new Set(ensureArray(relations[name]))]);
      }
      console.log('send', data);
      ws.send(JSON.stringify(data));
    };
    return {
      send,
    };
  };
}
