export type SSEMethod = "GET" | "POST" | "PUT" | "DELETE";

export interface SSEOptions {
  headers?: Record<string, string>;
  method?: SSEMethod;
  payload?: string;
  withCredentials?: boolean;
  timeout?: number;
}

type SSEOpenHandler = ((event?: Event) => void) | null;
type SSEMessageHandler = ((event: MessageEvent<string>) => void) | null;
type SSEErrorHandler = ((error: unknown) => void) | null;

function createTextDecoder() {
  if (typeof TextDecoder !== "undefined") {
    return new TextDecoder("utf-8");
  }
  return null;
}

function decodeBufferChunk(data: unknown, decoder: TextDecoder | null): string {
  if (typeof data === "string") {
    return data;
  }

  if (data instanceof ArrayBuffer) {
    if (decoder) {
      return decoder.decode(new Uint8Array(data), { stream: true });
    }
    return String.fromCharCode(...new Uint8Array(data));
  }

  if (ArrayBuffer.isView(data)) {
    const typedArray = data as Uint8Array;
    if (decoder) {
      return decoder.decode(typedArray, { stream: true });
    }
    return String.fromCharCode(...typedArray);
  }

  return "";
}

function parsePayload(payload?: string): string | Record<string, unknown> | ArrayBuffer | undefined {
  if (!payload) {
    return undefined;
  }
  try {
    return JSON.parse(payload);
  } catch {
    return payload;
  }
}

export class SSE {
  onopen: SSEOpenHandler = null;
  onmessage: SSEMessageHandler = null;
  onerror: SSEErrorHandler = null;

  private requestTask: UniApp.RequestTask | null = null;
  private abortController: AbortController | null = null;
  private buffer = "";
  private lastEventId = "";
  private closed = false;
  private opened = false;

  constructor(
    private readonly url: string,
    private readonly options: SSEOptions = {},
  ) {}

  stream() {
    this.closed = false;
    this.opened = false;
    this.buffer = "";

    if (typeof uni !== "undefined" && typeof uni.request === "function") {
      this.startWithUniRequest();
      return;
    }

    this.startWithFetch();
  }

  close() {
    this.closed = true;

    if (this.requestTask) {
      this.requestTask.abort();
      this.requestTask = null;
    }

    if (this.abortController) {
      this.abortController.abort();
      this.abortController = null;
    }
  }

  private emitOpen() {
    if (this.opened || this.closed) {
      return;
    }
    this.opened = true;
    this.onopen?.();
  }

  private emitError(error: unknown) {
    this.onerror?.(error);
    if (this.closed) {
      return;
    }
  }

  private dispatchRawEvent(raw: string) {
    if (!raw.trim()) {
      return;
    }

    const lines = raw.split("\n");
    const dataLines: string[] = [];
    let eventName = "message";

    for (const line of lines) {
      if (!line || line.startsWith(":")) {
        continue;
      }

      const separator = line.indexOf(":");
      const field = separator >= 0 ? line.slice(0, separator) : line;
      let value = separator >= 0 ? line.slice(separator + 1) : "";
      if (value.startsWith(" ")) {
        value = value.slice(1);
      }

      if (field === "data") {
        dataLines.push(value);
      } else if (field === "event" && value) {
        eventName = value;
      } else if (field === "id") {
        this.lastEventId = value;
      }
    }

    if (dataLines.length === 0) {
      return;
    }

    const event = {
      data: dataLines.join("\n"),
      type: eventName,
      lastEventId: this.lastEventId,
      origin: this.url,
    } as MessageEvent<string>;

    // 兼容 Chat.ts 的使用方式：统一通过 onmessage 消费数据。
    this.onmessage?.(event);
  }

  private consumeSseChunk(chunkText: string) {
    if (!chunkText || this.closed) {
      return;
    }

    this.emitOpen();
    this.buffer += chunkText.replace(/\r\n/g, "\n").replace(/\r/g, "\n");

    let boundaryIndex = this.buffer.indexOf("\n\n");
    while (boundaryIndex >= 0) {
      const rawEvent = this.buffer.slice(0, boundaryIndex);
      this.buffer = this.buffer.slice(boundaryIndex + 2);
      this.dispatchRawEvent(rawEvent);
      boundaryIndex = this.buffer.indexOf("\n\n");
    }
  }

  private flushRemainder() {
    if (this.buffer.trim()) {
      this.dispatchRawEvent(this.buffer);
      this.buffer = "";
    }
  }

  private startWithUniRequest() {
    const decoder = createTextDecoder();
    const method = this.options.method || "GET";
    const payloadData = parsePayload(this.options.payload);

    const requestTask = uni.request({
      url: this.url,
      method,
      header: this.options.headers,
      data: payloadData,
      timeout: this.options.timeout,
      responseType: "arraybuffer",
      enableChunked: true,
      success: (res) => {
        if (res.statusCode !== 200) {
          this.emitError(new Error(
            res.data ? JSON.stringify(res.data) : `请求失败: ${res.statusCode}`
          ));
          return;
        }
        if (this.closed) {
          return;
        }
        const text = decodeBufferChunk(res.data, decoder);
        this.consumeSseChunk(text);
        this.flushRemainder();
      },
      fail: (error) => {
        this.emitError(error);
      },
    } as UniApp.RequestOptions);

    this.requestTask = requestTask as unknown as UniApp.RequestTask;

    const chunkableTask = this.requestTask as UniApp.RequestTask & {
      onChunkReceived?: (callback: (result: { data: ArrayBuffer }) => void) => void;
    };
    if (typeof chunkableTask.onChunkReceived === "function") {
      chunkableTask.onChunkReceived((result: { data: ArrayBuffer }) => {
        if (this.closed) {
          return;
        }
        const text = decodeBufferChunk(result.data, decoder);
        this.consumeSseChunk(text);
      });
    }
  }

  private async startWithFetch() {
    if (typeof fetch === "undefined") {
      this.emitError(new Error("当前环境不支持 fetch 流式读取"));
      return;
    }

    this.abortController = new AbortController();
    const method = this.options.method || "GET";

    try {
      const response = await fetch(this.url, {
        method,
        headers: this.options.headers,
        body: this.options.payload,
        credentials: this.options.withCredentials ? "include" : "omit",
        signal: this.abortController.signal,
      });

      if (this.closed) {
        return;
      }

      if (!response.ok) {
        throw new Error(`请求失败: ${response.status} ${response.statusText}`);
      }

      this.emitOpen();

      if (!response.body) {
        const plainText = await response.text();
        this.consumeSseChunk(plainText);
        this.flushRemainder();
        return;
      }

      const reader = response.body.getReader();
      const decoder = createTextDecoder();

      while (!this.closed) {
        const result = await reader.read();
        if (result.done) {
          break;
        }
        const chunkText = decodeBufferChunk(result.value, decoder);
        this.consumeSseChunk(chunkText);
      }

      if (decoder) {
        const tail = decoder.decode();
        if (tail) {
          this.consumeSseChunk(tail);
        }
      }
      this.flushRemainder();
    } catch (error) {
      if (this.closed) {
        return;
      }
      this.emitError(error);
    }
  }
}
